在使用AWS Service的过程中,经常需要给AWS Service发http请求进行交互,比如对DynamoDB的CURD操作,S3上传或者下载文件等等。这些请求发送出去之后AWS会对请求中包含的Credentials进行验证,只有通过验证之后才会进行相应的操作,这就保证了安全性。
AWS Credentials包含以下四部分内容:
{
"AccessKeyId" : "*************",
"SecretAccessKey" : "*************************************",
"Token" : "***************************************************************************",
"Expiration" : "2021-11-12T10:51:36Z"
}
使用默认的CredentialsProviderChain:DefaultAWSCredentialsProviderChain
该方式时AWS官方推荐的方式
使用某个具体的CredentialsProvider或者CredentialsProviderChain,也可以根据自己的需求创建自己的CredentialsProvider或者CredentialsProviderChain
直接提供Credentials,Credentials可以是root账户的credentials,IAM用户的credentials,也可以是通过AWS STS服务获取到的temporary credentials
CredentialsProviderChain提供了四个CredentialsProvider:
package com.amazonaws.auth;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
/**
* AWS credentials provider chain that looks for credentials in this order:
* <ul>
* <li>Environment Variables -
* <code>AWS_ACCESS_KEY_ID</code> and <code>AWS_SECRET_ACCESS_KEY</code>
* (RECOMMENDED since they are recognized by all the AWS SDKs and CLI except for .NET),
* or <code>AWS_ACCESS_KEY</code> and <code>AWS_SECRET_KEY</code> (only recognized by Java SDK)
* </li>
* <li>Java System Properties - aws.accessKeyId and aws.secretKey</li>
* <li>Credential profiles file at the default location (~/.aws/credentials) shared by all AWS SDKs and the AWS CLI</li>
* <li>Credentials delivered through the Amazon EC2 container service if AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" environment variable is set
* and security manager has permission to access the variable,</li>
* <li>Instance profile credentials delivered through the Amazon EC2 metadata service</li>
* </ul>
*
* @see EnvironmentVariableCredentialsProvider
* @see SystemPropertiesCredentialsProvider
* @see ProfileCredentialsProvider
* @see EC2ContainerCredentialsProviderWrapper
*/
public class DefaultAWSCredentialsProviderChain extends AWSCredentialsProviderChain {
private static final DefaultAWSCredentialsProviderChain INSTANCE
= new DefaultAWSCredentialsProviderChain();
public DefaultAWSCredentialsProviderChain() {
super(new EnvironmentVariableCredentialsProvider(),
new SystemPropertiesCredentialsProvider(),
new ProfileCredentialsProvider(),
new EC2ContainerCredentialsProviderWrapper());
}
public static DefaultAWSCredentialsProviderChain getInstance() {
return INSTANCE;
}
}
package com.amazonaws.auth;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.amazonaws.SdkClientException;
/**
* {@link AWSCredentialsProvider} implementation that chains together multiple
* credentials providers. When a caller first requests credentials from this provider,
* it calls all the providers in the chain, in the original order specified,
* until one can provide credentials, and then returns those credentials. If all
* of the credential providers in the chain have been called, and none of them
* can provide credentials, then this class will throw an exception indicated
* that no credentials are available.
* <p>
* By default, this class will remember the first credentials provider in the chain
* that was able to provide credentials, and will continue to use that provider when
* credentials are requested in the future, instead of traversing the chain each time.
* This behavior can be controlled through the {@link #setReuseLastProvider(boolean)} method.
*/
public class AWSCredentialsProviderChain implements AWSCredentialsProvider {
private static final Log log = LogFactory.getLog(AWSCredentialsProviderChain.class);
private final List<AWSCredentialsProvider> credentialsProviders =
new LinkedList<AWSCredentialsProvider>();
private boolean reuseLastProvider = true;
private AWSCredentialsProvider lastUsedProvider;
/**
* Constructs a new AWSCredentialsProviderChain with the specified credential providers. When
* credentials are requested from this provider, it will call each of these credential providers
* in the same order specified here until one of them returns AWS security credentials.
*
* @param credentialsProviders
* The chain of credentials providers.
*/
public AWSCredentialsProviderChain(List<? extends AWSCredentialsProvider> credentialsProviders) {
if (credentialsProviders == null || credentialsProviders.size() == 0) {
throw new IllegalArgumentException("No credential providers specified");
}
this.credentialsProviders.addAll(credentialsProviders);
}
/**
* Constructs a new AWSCredentialsProviderChain with the specified credential providers. When
* credentials are requested from this provider, it will call each of these credential providers
* in the same order specified here until one of them returns AWS security credentials.
*
* @param credentialsProviders
* The chain of credentials providers.
*/
public AWSCredentialsProviderChain(AWSCredentialsProvider... credentialsProviders) {
if (credentialsProviders == null || credentialsProviders.length == 0) {
throw new IllegalArgumentException("No credential providers specified");
}
for (AWSCredentialsProvider provider : credentialsProviders) {
this.credentialsProviders.add(provider);
}
}
/**
* Returns true if this chain will reuse the last successful credentials
* provider for future credentials requests, otherwise, false if it will
* search through the chain each time.
*
* @return True if this chain will reuse the last successful credentials
* provider for future credentials requests.
*/
public boolean getReuseLastProvider() {
return reuseLastProvider;
}
/**
* Enables or disables caching of the last successful credentials provider
* in this chain. Reusing the last successful credentials provider will
* typically return credentials faster than searching through the chain.
*
* @param b
* Whether to enable or disable reusing the last successful
* credentials provider for future credentials requests instead
* of searching through the whole chain.
*/
public void setReuseLastProvider(boolean b) {
this.reuseLastProvider = b;
}
@Override
public AWSCredentials getCredentials() {
if (reuseLastProvider && lastUsedProvider != null) {
return lastUsedProvider.getCredentials();
}
List<String> exceptionMessages = null;
for (AWSCredentialsProvider provider : credentialsProviders) {
try {
AWSCredentials credentials = provider.getCredentials();
if (credentials.getAWSAccessKeyId() != null &&
credentials.getAWSSecretKey() != null) {
log.debug("Loading credentials from " + provider.toString());
lastUsedProvider = provider;
return credentials;
}
} catch (Exception e) {
// Ignore any exceptions and move onto the next provider
String message = provider + ": " + e.getMessage();
log.debug("Unable to load credentials from " + message);
if (exceptionMessages == null) {
exceptionMessages = new LinkedList<String>();
}
exceptionMessages.add(message);
}
}
throw new SdkClientException("Unable to load AWS credentials from any provider in the chain: "
+ exceptionMessages);
}
@Override
public void refresh() {
for (AWSCredentialsProvider provider : credentialsProviders) {
provider.refresh();
}
}
}
package com.amazonaws.auth;
import static com.amazonaws.SDKGlobalConfiguration.ACCESS_KEY_ENV_VAR;
import static com.amazonaws.SDKGlobalConfiguration.ALTERNATE_ACCESS_KEY_ENV_VAR;
import static com.amazonaws.SDKGlobalConfiguration.ALTERNATE_SECRET_KEY_ENV_VAR;
import static com.amazonaws.SDKGlobalConfiguration.AWS_SESSION_TOKEN_ENV_VAR;
import static com.amazonaws.SDKGlobalConfiguration.SECRET_KEY_ENV_VAR;
import com.amazonaws.SdkClientException;
import com.amazonaws.util.StringUtils;
/**
* {@link AWSCredentialsProvider} implementation that provides credentials by looking at the: <code>AWS_ACCESS_KEY_ID</code> (or
* <code>AWS_ACCESS_KEY</code>) and <code>AWS_SECRET_KEY</code> (or <code>AWS_SECRET_ACCESS_KEY</code>) environment variables. If
* the <code>AWS_SESSION_TOKEN</code> environment variable is also set then temporary credentials will be used.
*/
public class EnvironmentVariableCredentialsProvider implements AWSCredentialsProvider {
@Override
public AWSCredentials getCredentials() {
String accessKey = System.getenv(ACCESS_KEY_ENV_VAR);
if (accessKey == null) {
accessKey = System.getenv(ALTERNATE_ACCESS_KEY_ENV_VAR);
}
String secretKey = System.getenv(SECRET_KEY_ENV_VAR);
if (secretKey == null) {
secretKey = System.getenv(ALTERNATE_SECRET_KEY_ENV_VAR);
}
accessKey = StringUtils.trim(accessKey);
secretKey = StringUtils.trim(secretKey);
String sessionToken = StringUtils.trim(System.getenv(AWS_SESSION_TOKEN_ENV_VAR));
if (StringUtils.isNullOrEmpty(accessKey) || StringUtils.isNullOrEmpty(secretKey)) {
throw new SdkClientException(
"Unable to load AWS credentials from environment variables " +
"(" + ACCESS_KEY_ENV_VAR + " (or " + ALTERNATE_ACCESS_KEY_ENV_VAR + ") and " +
SECRET_KEY_ENV_VAR + " (or " + ALTERNATE_SECRET_KEY_ENV_VAR + "))");
}
return sessionToken == null ?
new BasicAWSCredentials(accessKey, secretKey)
:
new BasicSessionCredentials(accessKey, secretKey, sessionToken);
}
@Override
public void refresh() {
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}
package com.amazonaws.auth;
import static com.amazonaws.SDKGlobalConfiguration.ACCESS_KEY_SYSTEM_PROPERTY;
import static com.amazonaws.SDKGlobalConfiguration.SECRET_KEY_SYSTEM_PROPERTY;
import static com.amazonaws.SDKGlobalConfiguration.SESSION_TOKEN_SYSTEM_PROPERTY;
import com.amazonaws.SdkClientException;
import com.amazonaws.util.StringUtils;
/**
* {@link AWSCredentialsProvider} implementation that provides credentials by
* looking at the <code>aws.accessKeyId</code> and <code>aws.secretKey</code>
* Java system properties.
*/
public class SystemPropertiesCredentialsProvider implements AWSCredentialsProvider {
@Override
public AWSCredentials getCredentials() {
String accessKey = StringUtils.trim(System.getProperty(ACCESS_KEY_SYSTEM_PROPERTY));
String secretKey = StringUtils.trim(System.getProperty(SECRET_KEY_SYSTEM_PROPERTY));
String sessionToken = StringUtils.trim(System.getProperty(SESSION_TOKEN_SYSTEM_PROPERTY));
if (StringUtils.isNullOrEmpty(accessKey) || StringUtils.isNullOrEmpty(secretKey)) {
throw new SdkClientException(
"Unable to load AWS credentials from Java system "
+ "properties (" + ACCESS_KEY_SYSTEM_PROPERTY + " and "
+ SECRET_KEY_SYSTEM_PROPERTY + ")");
}
if (StringUtils.isNullOrEmpty(sessionToken)) {
return new BasicAWSCredentials(accessKey, secretKey);
} else {
return new BasicSessionCredentials(accessKey, secretKey, sessionToken);
}
}
@Override
public void refresh() {
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}
package com.amazonaws.auth.profile;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.profile.internal.AwsProfileNameLoader;
import java.util.concurrent.Semaphore;
/**
* Credentials provider based on AWS configuration profiles. This provider vends AWSCredentials from
* the profile configuration file for the default profile, or for a specific, named profile. <p> AWS
* credential profiles allow you to share multiple sets of AWS security credentials between
* different tools like the AWS SDK for Java and the AWS CLI. <p> See
* http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html
*
* @see ProfilesConfigFile
*/
public class ProfileCredentialsProvider implements AWSCredentialsProvider {
/**
* Default refresh interval
*/
private static final long DEFAULT_REFRESH_INTERVAL_NANOS = 5 * 60 * 1000 * 1000 * 1000L;
/**
* Default force reload interval
*/
private static final long DEFAULT_FORCE_RELOAD_INTERVAL_NANOS =
2 * DEFAULT_REFRESH_INTERVAL_NANOS;
/**
* The credential profiles file from which this provider loads the security credentials. Lazily
* loaded by the double-check idiom.
*/
private volatile ProfilesConfigFile profilesConfigFile;
/**
* When the profiles file was last refreshed.
*/
private volatile long lastRefreshed;
/**
* The name of the credential profile
*/
private final String profileName;
/**
* Used to have only one thread block on refresh, for applications making at least one call
* every REFRESH_INTERVAL_NANOS.
*/
private final Semaphore refreshSemaphore = new Semaphore(1);
/**
* Refresh interval. Defaults to {@link #DEFAULT_REFRESH_INTERVAL_NANOS}
*/
private long refreshIntervalNanos = DEFAULT_REFRESH_INTERVAL_NANOS;
/**
* Force reload interval. Defaults to {@link #DEFAULT_FORCE_RELOAD_INTERVAL_NANOS}
*/
private long refreshForceIntervalNanos = DEFAULT_FORCE_RELOAD_INTERVAL_NANOS;
/**
* Creates a new profile credentials provider that returns the AWS security credentials
* configured for the default profile. Loading the credential file is deferred until the
* getCredentials() method is called.
*/
public ProfileCredentialsProvider() {
this(null);
}
/**
* Creates a new profile credentials provider that returns the AWS security credentials
* configured for the named profile. Loading the credential file is deferred until the
* getCredentials() method is called.
*
* @param profileName The name of a local configuration profile.
*/
public ProfileCredentialsProvider(String profileName) {
this((ProfilesConfigFile) null, profileName);
}
/**
* Creates a new profile credentials provider that returns the AWS security credentials for the
* specified profiles configuration file and profile name.
*
* @param profilesConfigFilePath The file path where the profile configuration file is located.
* @param profileName The name of a configuration profile in the specified
* configuration file.
*/
public ProfileCredentialsProvider(String profilesConfigFilePath, String profileName) {
this(new ProfilesConfigFile(profilesConfigFilePath), profileName);
}
/**
* Creates a new profile credentials provider that returns the AWS security credentials for the
* specified profiles configuration file and profile name.
*
* @param profilesConfigFile The profile configuration file containing the profiles used by this
* credentials provider or null to defer load to first use.
* @param profileName The name of a configuration profile in the specified configuration
* file.
*/
public ProfileCredentialsProvider(ProfilesConfigFile profilesConfigFile, String profileName) {
this.profilesConfigFile = profilesConfigFile;
if (this.profilesConfigFile != null) {
this.lastRefreshed = System.nanoTime();
}
if (profileName == null) {
this.profileName = AwsProfileNameLoader.INSTANCE.loadProfileName();
} else {
this.profileName = profileName;
}
}
@Override
public AWSCredentials getCredentials() {
if (profilesConfigFile == null) {
synchronized (this) {
if (profilesConfigFile == null) {
profilesConfigFile = new ProfilesConfigFile();
lastRefreshed = System.nanoTime();
}
}
}
// Periodically check if the file on disk has been modified
// since we last read it.
//
// For active applications, only have one thread block.
// For applications that use this method in bursts, ensure the
// credentials are never too stale.
long now = System.nanoTime();
long age = now - lastRefreshed;
if (age > refreshForceIntervalNanos) {
refresh();
} else if (age > refreshIntervalNanos) {
if (refreshSemaphore.tryAcquire()) {
try {
refresh();
} finally {
refreshSemaphore.release();
}
}
}
return profilesConfigFile.getCredentials(profileName);
}
@Override
public void refresh() {
if (profilesConfigFile != null) {
profilesConfigFile.refresh();
lastRefreshed = System.nanoTime();
}
}
/**
* Gets the refresh interval in nanoseconds.
*
* @return nanoseconds
*/
public long getRefreshIntervalNanos() {
return refreshIntervalNanos;
}
/**
* Sets the refresh interval in nanoseconds.
*
* @param refreshIntervalNanos nanoseconds
*/
public void setRefreshIntervalNanos(long refreshIntervalNanos) {
this.refreshIntervalNanos = refreshIntervalNanos;
}
/**
* Gets the forced refresh interval in nanoseconds.
*
* @return nanoseconds
*/
public long getRefreshForceIntervalNanos() {
return refreshForceIntervalNanos;
}
/**
* Sets the forced refresh interval in nanoseconds.
*/
public void setRefreshForceIntervalNanos(long refreshForceIntervalNanos) {
this.refreshForceIntervalNanos = refreshForceIntervalNanos;
}
}
package com.amazonaws.auth;
import static com.amazonaws.auth.ContainerCredentialsProvider.CONTAINER_CREDENTIALS_FULL_URI;
import static com.amazonaws.auth.ContainerCredentialsProvider.ECS_CONTAINER_CREDENTIALS_PATH;
import com.amazonaws.auth.ContainerCredentialsProvider.ECSCredentialsEndpointProvider;
import com.amazonaws.auth.ContainerCredentialsProvider.FullUriCredentialsEndpointProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <p>
* {@link AWSCredentialsProvider} that loads credentials from an Amazon Container (e.g. EC2)
*
* Credentials are solved in the following order:
* <ol>
* <li>
* If environment variable "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" is
* set (typically on EC2) it is used to hit the metadata service at the following endpoint: http://169.254.170.2
* </li>
* <li>
* If environment variable "AWS_CONTAINER_CREDENTIALS_FULL_URI" is
* set it is used to hit a metadata service at that URI. <br/> Optionally an authorization token can be included
* in the "Authorization" header of the request by setting the "AWS_CONTAINER_AUTHORIZATION_TOKEN" environment variable.
* </li>
* <li>
* If neither of the above environment variables are specified credentials are attempted to be loaded from Amazon EC2
* Instance Metadata Service using the {@link InstanceProfileCredentialsProvider}.
* </li>
* </ol>
*/
public class EC2ContainerCredentialsProviderWrapper implements AWSCredentialsProvider {
private static final Log LOG = LogFactory.getLog(EC2ContainerCredentialsProviderWrapper.class);
private final AWSCredentialsProvider provider;
public EC2ContainerCredentialsProviderWrapper() {
provider = initializeProvider();
}
private AWSCredentialsProvider initializeProvider() {
try {
if (System.getenv(ECS_CONTAINER_CREDENTIALS_PATH) != null) {
return new ContainerCredentialsProvider(new ECSCredentialsEndpointProvider());
}
if (System.getenv(CONTAINER_CREDENTIALS_FULL_URI) != null) {
return new ContainerCredentialsProvider(new FullUriCredentialsEndpointProvider());
}
return InstanceProfileCredentialsProvider.getInstance();
} catch (SecurityException securityException) {
LOG.debug("Security manager did not allow access to the ECS credentials environment variable " + ECS_CONTAINER_CREDENTIALS_PATH +
"or the container full URI environment variable " + CONTAINER_CREDENTIALS_FULL_URI
+ ". Please provide access to this environment variable if you want to load credentials from ECS Container.");
return InstanceProfileCredentialsProvider.getInstance();
}
}
@Override
public AWSCredentials getCredentials() {
return provider.getCredentials();
}
@Override
public void refresh() {
provider.refresh();
}
}
package com.amazonaws.auth;
import com.amazonaws.SdkClientException;
import com.amazonaws.internal.CredentialsEndpointProvider;
import com.amazonaws.retry.internal.CredentialsEndpointRetryPolicy;
import com.amazonaws.util.CollectionUtils;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* <p>
* {@link AWSCredentialsProvider} implementation that loads credentials
* from an Amazon Elastic Container.
* </p>
* <p>
* By default, the URI path is retrieved from the environment variable
* "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" in the container's environment.
* </p>
*/
public class ContainerCredentialsProvider implements AWSCredentialsProvider {
/** Environment variable to get the Amazon ECS credentials resource path. */
static final String ECS_CONTAINER_CREDENTIALS_PATH = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI";
/** Environment variable to get the full URI for a credentials path */
static final String CONTAINER_CREDENTIALS_FULL_URI = "AWS_CONTAINER_CREDENTIALS_FULL_URI";
static final String CONTAINER_AUTHORIZATION_TOKEN = "AWS_CONTAINER_AUTHORIZATION_TOKEN";
private static final Set<String> ALLOWED_FULL_URI_HOSTS = allowedHosts();
/** Default endpoint to retreive the Amazon ECS Credentials. */
private static final String ECS_CREDENTIALS_ENDPOINT = "http://169.254.170.2";
private final EC2CredentialsFetcher credentialsFetcher;
/**
* @deprecated use {@link #ContainerCredentialsProvider(CredentialsEndpointProvider)}
*/
@Deprecated
public ContainerCredentialsProvider() {
this(new ECSCredentialsEndpointProvider());
}
public ContainerCredentialsProvider(CredentialsEndpointProvider credentialsEndpointProvider) {
this.credentialsFetcher = new EC2CredentialsFetcher(credentialsEndpointProvider);
}
@Override
public AWSCredentials getCredentials() {
return credentialsFetcher.getCredentials();
}
@Override
public void refresh() {
credentialsFetcher.refresh();
}
public Date getCredentialsExpiration() {
return credentialsFetcher.getCredentialsExpiration();
}
static class ECSCredentialsEndpointProvider extends CredentialsEndpointProvider {
@Override
public URI getCredentialsEndpoint() throws URISyntaxException {
String path = System.getenv(ECS_CONTAINER_CREDENTIALS_PATH);
if (path == null) {
throw new SdkClientException(
"The environment variable " + ECS_CONTAINER_CREDENTIALS_PATH + " is empty");
}
return new URI(ECS_CREDENTIALS_ENDPOINT + path);
}
@Override
public CredentialsEndpointRetryPolicy getRetryPolicy() {
return ContainerCredentialsRetryPolicy.getInstance();
}
}
/**
* A URI resolver that uses environment variable {@value CONTAINER_CREDENTIALS_FULL_URI} as the URI
* for the metadata service.
* Optionally an authorization token can be provided using the {@value CONTAINER_AUTHORIZATION_TOKEN} environment variable.
*/
static class FullUriCredentialsEndpointProvider extends CredentialsEndpointProvider {
@Override
public URI getCredentialsEndpoint() throws URISyntaxException {
String fullUri = System.getenv(CONTAINER_CREDENTIALS_FULL_URI);
if (fullUri == null || fullUri.length() == 0) {
throw new SdkClientException("The environment variable " + CONTAINER_CREDENTIALS_FULL_URI + " is empty");
}
URI uri = new URI(fullUri);
if (!ALLOWED_FULL_URI_HOSTS.contains(uri.getHost())) {
throw new SdkClientException("The full URI (" + uri + ") contained withing environment variable " +
CONTAINER_CREDENTIALS_FULL_URI + " has an invalid host. Host can only be one of [" +
CollectionUtils.join(ALLOWED_FULL_URI_HOSTS, ", ") + "]");
}
return uri;
}
@Override
public Map<String, String> getHeaders() {
if (System.getenv(CONTAINER_AUTHORIZATION_TOKEN) != null) {
return Collections.singletonMap("Authorization", System.getenv(CONTAINER_AUTHORIZATION_TOKEN));
}
return new HashMap<String, String>();
}
}
private static Set<String> allowedHosts() {
HashSet<String> hosts = new HashSet<String>();
hosts.add("127.0.0.1");
hosts.add("localhost");
return Collections.unmodifiableSet(hosts);
}
}
package com.amazonaws.auth;
import com.amazonaws.AmazonClientException;
import com.amazonaws.SDKGlobalConfiguration;
import com.amazonaws.SdkClientException;
import com.amazonaws.internal.CredentialsEndpointProvider;
import com.amazonaws.internal.EC2CredentialsUtils;
import com.amazonaws.util.EC2MetadataUtils;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Credentials provider implementation that loads credentials from the Amazon EC2 Instance Metadata Service.
*
* <p>When using {@link InstanceProfileCredentialsProvider} with asynchronous refreshing it is
* <b>strongly</b> recommended to explicitly call {@link #close()} to release the async thread.</p>
*/
public class InstanceProfileCredentialsProvider implements AWSCredentialsProvider, Closeable {
private static final Log LOG = LogFactory.getLog(InstanceProfileCredentialsProvider.class);
/**
* The wait time, after which the background thread initiates a refresh to
* load latest credentials if needed.
*/
private static final int ASYNC_REFRESH_INTERVAL_TIME_MINUTES = 1;
/**
* The default InstanceProfileCredentialsProvider that can be shared by
* multiple CredentialsProvider instance threads to shrink the amount of
* requests to EC2 metadata service.
*/
private static final InstanceProfileCredentialsProvider INSTANCE = new InstanceProfileCredentialsProvider();
private final EC2CredentialsFetcher credentialsFetcher;
/**
* The executor service used for refreshing the credentials in the
* background.
*/
private volatile ScheduledExecutorService executor;
private volatile boolean shouldRefresh = false;
/**
* @deprecated for the singleton method {@link #getInstance()}.
*/
@Deprecated
public InstanceProfileCredentialsProvider() {
this(false);
}
/**
* Spins up a new thread to refresh the credentials asynchronously if
* refreshCredentialsAsync is set to true, otherwise the credentials will be
* refreshed from the instance metadata service synchronously,
*
* <p>It is <b>strongly</b> recommended to reuse instances of this credentials provider, especially
* when async refreshing is used since a background thread is created.</p>
*
* @param refreshCredentialsAsync
* true if credentials needs to be refreshed asynchronously else
* false.
*/
public InstanceProfileCredentialsProvider(boolean refreshCredentialsAsync) {
this(refreshCredentialsAsync, true);
}
/**
* Spins up a new thread to refresh the credentials asynchronously.
*
* <p>It is <b>strongly</b> recommended to reuse instances of this credentials provider, especially
* when async refreshing is used since a background thread is created.</p>
*
* @param eagerlyRefreshCredentialsAsync
* when set to false will not attempt to refresh credentials asynchronously
* until after a call has been made to {@link #getCredentials()} - ensures that
* {@link EC2CredentialsFetcher#getCredentials()} is only hit when this CredentialProvider is actually required
*/
public static InstanceProfileCredentialsProvider createAsyncRefreshingProvider(final boolean eagerlyRefreshCredentialsAsync) {
return new InstanceProfileCredentialsProvider(true, eagerlyRefreshCredentialsAsync);
}
private InstanceProfileCredentialsProvider(boolean refreshCredentialsAsync, final boolean eagerlyRefreshCredentialsAsync) {
credentialsFetcher = new EC2CredentialsFetcher(new InstanceMetadataCredentialsEndpointProvider());
if (!SDKGlobalConfiguration.isEc2MetadataDisabled()) {
if (refreshCredentialsAsync) {
executor = Executors.newScheduledThreadPool(1);
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
if (shouldRefresh) credentialsFetcher.getCredentials();
} catch (AmazonClientException ace) {
handleError(ace);
} catch (RuntimeException re) {
handleError(re);
}
}
}, 0, ASYNC_REFRESH_INTERVAL_TIME_MINUTES, TimeUnit.MINUTES);
}
}
}
/**
* Returns a singleton {@link InstanceProfileCredentialsProvider} that does not refresh credentials asynchronously.
*
* <p>
* See {@link #InstanceProfileCredentialsProvider(boolean)} or {@link #createAsyncRefreshingProvider(boolean)} for
* asynchronous credentials refreshing.
* </p>
*/
public static InstanceProfileCredentialsProvider getInstance() {
return INSTANCE;
}
private void handleError(Throwable t) {
refresh();
LOG.error(t.getMessage(), t);
}
@Override
protected void finalize() throws Throwable {
if (executor != null) {
executor.shutdownNow();
}
}
/**
* {@inheritDoc}
*
* @throws AmazonClientException if {@link SDKGlobalConfiguration#isEc2MetadataDisabled()} is true
*/
@Override
public AWSCredentials getCredentials() {
if (SDKGlobalConfiguration.isEc2MetadataDisabled()) {
throw new AmazonClientException("AWS_EC2_METADATA_DISABLED is set to true, not loading credentials from EC2 Instance "
+ "Metadata service");
}
AWSCredentials creds = credentialsFetcher.getCredentials();
shouldRefresh = true;
return creds;
}
@Override
public void refresh() {
if (credentialsFetcher != null) {
credentialsFetcher.refresh();
}
}
@Override
public void close() throws IOException {
if (executor != null) {
executor.shutdownNow();
executor = null;
}
}
private static class InstanceMetadataCredentialsEndpointProvider extends CredentialsEndpointProvider {
@Override
public URI getCredentialsEndpoint() throws URISyntaxException, IOException {
String host = EC2MetadataUtils.getHostAddressForEC2MetadataService();
String securityCredentialsList = EC2CredentialsUtils.getInstance().readResource(new URI(host + EC2MetadataUtils.SECURITY_CREDENTIALS_RESOURCE));
String[] securityCredentials = securityCredentialsList.trim().split("\n");
if (securityCredentials.length == 0) {
throw new SdkClientException("Unable to load credentials path");
}
return new URI(host + EC2MetadataUtils.SECURITY_CREDENTIALS_RESOURCE + securityCredentials[0]);
}
}
}