

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 使用 IAM 进行身份验证
<a name="auth-iam"></a>

**Topics**
+ [概述](#auth-iam-overview)
+ [限制](#auth-iam-limits)
+ [设置](#auth-iam-setup)
+ [连接](#auth-iam-Connecting)

## 概述
<a name="auth-iam-overview"></a>

使用 IAM 身份验证，当您的缓存配置 ElastiCache 为使用 Valkey 或 Redis OSS 版本 7 或更高版本时，您可以使用 AWS IAM 身份验证与 Valkey 或 Redis OSS 的连接。这使您可以增强安全模型并简化许多管理安全任务。您还可以使用 IAM 身份验证按照最低权限原则为每个 ElastiCache 缓存和 ElastiCache 用户配置精细的访问控制。IAM 身份验证 ElastiCache 的工作原理是在 Valkey 或 Redis OSS 或命令中提供短期的 IAM 身份验证令牌，而不是长期存在的 ElastiCache 用户密码。`AUTH` `HELLO`有关 IAM 身份验证令牌的更多信息，请参阅《 AWS 通用参考指南》中的[签名版本 4 签名流程](https://docs.aws.amazon.com//general/latest/gr/signature-version-4.html)和下面的代码示例。

您可以使用 IAM 身份及其关联策略进一步限制 Valkey 或 Redis OSS 访问权限。您还可以直接从联合身份提供商向用户授予对 Valkey 或 Redis OSS 缓存的访问权限。

要 AWS 将 IAM 与配合使用 ElastiCache，您首先需要创建一个身份验证模式设置为 IAM 的 ElastiCache 用户。然后，您可以创建或重复使用 IAM 身份。IAM 身份需要关联策略才能将`elasticache:Connect`操作授予 ElastiCache 缓存和 ElastiCache 用户。配置完成后，您可以使用 IAM 用户或角色的 AWS 证书创建 IAM 身份验证令牌。最后，在连接到您的缓存时，您需要在 Valkey 或 Redis OSS 客户端中提供有效期较短的 IAM 身份验证令牌作为密码。支持凭证提供程序的 Valkey 或 Redis OSS 客户端可以自动为每个新连接生成临时证书。 ElastiCache 将对 IAM-enabled ElastiCache 用户的连接请求执行 IAM 身份验证，并将通过 IAM 验证连接请求。

## 限制
<a name="auth-iam-limits"></a>

使用 IAM 身份验证时，以下限制适用：
+ 用 ElastiCache 于 Valkey 7.2 及以上版本或 Redis OSS 7.0 及以上版本时，IAM 身份验证可用。
+ IAM 身份验证需要在您的缓存上启用传输中加密 (TLS)。有关更多信息，请参阅 [ElastiCache 传输中加密 (TLS)](in-transit-encryption.md)。
+ 对于 IAM-enabled ElastiCache 用户，用户名和用户 ID 属性必须相同。
+ IAM 身份验证令牌的有效期为 15 分钟。如果使用过期的令牌重新验证连接，则身份验证请求将被拒绝。对于长期连接，我们建议使用支持凭证提供程序接口的 Valkey 或 Redis OSS 客户端，在到期前自动生成新的令牌。
+ 通过 IAM 身份验证的 Valkey 或 Redis OSS 连接将在 12 小时后自动断开。 ElastiCache 通过使用新 IAM 身份验证令牌发送 `AUTH` 或 `HELLO` 命令，可以将连接延长 12 小时。
+ `MULTI`/`AUTH`或 Lua 脚本块中不支持 IAM 重新身份验证（`EXEC`或`HELLO`命令）。但是，你可以在 IAM-authenticated 连接上的`MULTI`/`EXEC`块内运行常规数据命令。
+ 目前，IAM 身份验证支持以下全局条件上下文键：
  + 对无服务器缓存使用 IAM 身份验证时，支持 `aws:VpcSourceIp`、`aws:SourceVpc`、`aws:SourceVpce`、`aws:CurrentTime`、`aws:EpochTime` 和 `aws:ResourceTag/%s`（从关联的无服务器缓存和用户）。
  + 对复制组使用 IAM 身份验证时，支持 `aws:SourceIp` 和 `aws:ResourceTag/%s`（从关联的复制组和用户）。

  有关全局条件上下文键的更多信息，请参阅《IAM 用户指南》中的 [AWS 全局条件上下文键](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html)。

**注意**  
缓存名称在创建缓存时会转换为小写。确保身份验证代码以小写形式提供缓存名称，以避免身份验证错误。

## 设置
<a name="auth-iam-setup"></a>

要设置 IAM 身份验证，请执行以下操作：

1. 创建缓存

   ```
   aws elasticache create-serverless-cache \
     --serverless-cache-name cache-01  \
     --description "ElastiCache IAM auth application" \
     --engine redis
   ```

1. 为您的角色创建 IAM 信任政策文档，如下所示，允许您的账户承担新角色。将策略保存到名为 *trust-policy.json* 的文件中。

------
#### [ JSON ]

   ```
   {
       "Version": "2012-10-17",
       "Statement": [
           {
               "Effect": "Allow",
               "Principal": {
                   "AWS": "arn:aws:iam::{{123456789012}}:role/{{my-application-role}}"
               },
               "Action": "sts:AssumeRole"
           }
       ]
   }
   ```

**注意**  
`{{my-application-role}}`替换为需要连接到缓存的 IAM 角色或用户。使用权限最低的委托人而不是账户根来限制哪些身份可以担任此角色。

------

1. 创建 IAM 策略文档，如下所示。将策略保存到名为 *policy.json* 的文件中。

------
#### [ JSON ]

****  

   ```
   {
     "Version":"2012-10-17",		 	 	 
     "Statement": [
       {
         "Effect" : "Allow",
         "Action" : [
           "elasticache:Connect"
         ],
         "Resource" : [
           "arn:aws:elasticache:us-east-1:123456789012:serverlesscache:cache-01",
           "arn:aws:elasticache:us-east-1:123456789012:user:iam-user-01"
         ]
       }
     ]
   }
   ```

------

1. 创建 IAM 角色。

   ```
   aws iam create-role \
   --role-name "elasticache-iam-auth-app" \
   --assume-role-policy-document file://trust-policy.json
   ```

1. 创建 IAM 策略。

   ```
   aws iam create-policy \
     --policy-name "elasticache-allow-all" \
     --policy-document file://policy.json
   ```

1. 向角色附加 IAM 策略。

   ```
   aws iam attach-role-policy \
    --role-name "elasticache-iam-auth-app" \
    --policy-arn "arn:aws:iam::123456789012:policy/elasticache-allow-all"
   ```

1. 创建新 IAM-enabled 用户。

   ```
   aws elasticache create-user \
     --user-name iam-user-01 \
     --user-id iam-user-01 \
     --authentication-mode Type=iam \
     --engine redis \
     --access-string "on ~* +@all"
   ```

1. 创建用户组并附加用户。

   ```
   aws elasticache create-user-group \
     --user-group-id iam-user-group-01 \
     --engine redis \
     --user-ids default iam-user-01
   
   aws elasticache modify-serverless-cache \
     --serverless-cache-name cache-01  \
     --user-group-id iam-user-group-01
   ```

## 连接
<a name="auth-iam-Connecting"></a>

**使用令牌作为密码进行连接**

您首先需要使用 [AWS SigV4 预签名请求](https://docs.aws.amazon.com//general/latest/gr/sigv4-signed-request-examples.html)生成有效期较短的 IAM 身份验证令牌。之后，您需要在连接到 Valkey 或 Redis OSS 缓存时提供 IAM 身份验证令牌作为密码，如下例所示。

```
String userId = "{{insert user id}}";
String cacheName = "{{insert cache name}}";
boolean isServerless = {{true}};
String region = "{{insert region}}";

// Create a default AWS Credentials provider.
// This will look for AWS credentials defined in environment variables or system properties.
AwsCredentialsProvider awsCredentialsProvider = DefaultCredentialsProvider.create();

// Create an IAM authentication token request and signed it using the AWS credentials.
// The pre-signed request URL is used as an IAM authentication token for ElastiCache with Redis OSS.
IAMAuthTokenRequest iamAuthTokenRequest = new IAMAuthTokenRequest(userId, cacheName, region, isServerless);
String iamAuthToken = iamAuthTokenRequest.toSignedRequestUri(awsCredentialsProvider.resolveCredentials());

// Construct Redis OSS URL with IAM Auth credentials provider
RedisURI redisURI = RedisURI.builder()
    .withHost(host)
    .withPort(port)
    .withSsl(ssl)
    .withAuthentication(userId, iamAuthToken)
    .build();

// Create a new Lettuce Redis OSS client
RedisClient client = RedisClient.create(redisURI);
client.connect();
```

以下为 `IAMAuthTokenRequest` 的定义。

```
public class IAMAuthTokenRequest {
    private static final SdkHttpMethod REQUEST_METHOD = SdkHttpMethod.GET;
    private static final String REQUEST_PROTOCOL = "http://";
    private static final String PARAM_ACTION = "Action";
    private static final String PARAM_USER = "User";
    private static final String PARAM_RESOURCE_TYPE = "ResourceType";
    private static final String RESOURCE_TYPE_SERVERLESS_CACHE = "ServerlessCache";
    private static final String ACTION_NAME = "connect";
    private static final String SERVICE_NAME = "elasticache";
    private static final Duration TOKEN_EXPIRY_DURATION = Duration.ofSeconds(900);

    private final String userId;
    private final String cacheName;
    private final String region;
    private final boolean isServerless;

    public IAMAuthTokenRequest(String userId, String cacheName, String region, boolean isServerless) {
        this.userId = userId;
        this.cacheName = cacheName;
        this.region = region;
        this.isServerless = isServerless;
    }

    public String toSignedRequestUri(AwsCredentials credentials) {
        SdkHttpFullRequest request = getSignableRequest();
        SdkHttpFullRequest signedRequest = sign(request, credentials);
        return signedRequest.getUri().toString().replace(REQUEST_PROTOCOL, "");
    }

    private SdkHttpFullRequest getSignableRequest() {
        SdkHttpFullRequest.Builder builder = SdkHttpFullRequest.builder()
            .method(REQUEST_METHOD)
            .uri(getRequestUri())
            .appendRawQueryParameter(PARAM_ACTION, ACTION_NAME)
            .appendRawQueryParameter(PARAM_USER, userId);
        if (isServerless) {
            builder.appendRawQueryParameter(PARAM_RESOURCE_TYPE, RESOURCE_TYPE_SERVERLESS_CACHE);
        }
        return builder.build();
    }

    private URI getRequestUri() {
        return URI.create(String.format("%s%s/", REQUEST_PROTOCOL, cacheName));
    }

    private SdkHttpFullRequest sign(SdkHttpFullRequest request, AwsCredentials credentials) {
        AwsV4HttpSigner signer = AwsV4HttpSigner.create();
        SignedRequest signedRequest = signer.sign(r -> r.identity(credentials)
            .request(request)
            .putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, SERVICE_NAME)
            .putProperty(AwsV4HttpSigner.REGION_NAME, region)
            .putProperty(AwsV4HttpSigner.AUTH_LOCATION, AwsV4HttpSigner.AuthLocation.QUERY_STRING)
            .putProperty(AwsV4HttpSigner.EXPIRATION_DURATION, TOKEN_EXPIRY_DURATION)
            .build()
        );
        return (SdkHttpFullRequest) signedRequest.request();
    }
}
```

**使用凭证提供程序进行连接**

以下代码显示了如何 ElastiCache 使用 IAM 身份验证凭证提供商进行身份验证。

```
String userId = "{{insert user id}}";
String cacheName = "{{insert cache name}}";
boolean isServerless = {{true}};
String region = "{{insert region}}";

// Create a default AWS Credentials provider.
// This will look for AWS credentials defined in environment variables or system properties.
AwsCredentialsProvider awsCredentialsProvider = DefaultCredentialsProvider.create();

// Create an IAM authentication token request. Once this request is signed it can be used as an
// IAM authentication token for ElastiCache with Redis OSS.
IAMAuthTokenRequest iamAuthTokenRequest = new IAMAuthTokenRequest(userId, cacheName, region, isServerless);

// Create a Redis OSS credentials provider using IAM credentials.
RedisCredentialsProvider redisCredentialsProvider = new RedisIAMAuthCredentialsProvider(
    userId, iamAuthTokenRequest, awsCredentialsProvider);
    
// Construct Redis OSS URL with IAM Auth credentials provider
RedisURI redisURI = RedisURI.builder()
    .withHost(host)
    .withPort(port)
    .withSsl(ssl)
    .withAuthentication(redisCredentialsProvider)
    .build();

// Create a new Lettuce Redis OSS client
RedisClient client = RedisClient.create(redisURI);
client.connect();
```

以下是 Lettuce Redis OSS 客户端的示例，该客户端将封装在凭证提供程序 IAMAuthTokenRequest 中，以便在需要时自动生成临时证书。

```
public class RedisIAMAuthCredentialsProvider implements RedisCredentialsProvider {
    private static final long TOKEN_EXPIRY_SECONDS = 900;

    private final AwsCredentialsProvider awsCredentialsProvider;
    private final String userId;
    private final IAMAuthTokenRequest iamAuthTokenRequest;
    private final Supplier<String> iamAuthTokenSupplier;

    public RedisIAMAuthCredentialsProvider(String userId,
        IAMAuthTokenRequest iamAuthTokenRequest,
        AwsCredentialsProvider awsCredentialsProvider) {
        this.userId = userId;
        this.awsCredentialsProvider = awsCredentialsProvider;
        this.iamAuthTokenRequest = iamAuthTokenRequest;      
        this.iamAuthTokenSupplier = Suppliers.memoizeWithExpiration(this::getIamAuthToken, TOKEN_EXPIRY_SECONDS, TimeUnit.SECONDS);
    }

    @Override
    public Mono<RedisCredentials> resolveCredentials() {
        return Mono.just(RedisCredentials.just(userId, iamAuthTokenSupplier.get()));
    }

    private String getIamAuthToken() {
        return iamAuthTokenRequest.toSignedRequestUri(awsCredentialsProvider.resolveCredentials());
    }
}
```