广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何通过Web Identity Federation为DynamoDB实现安全的联合身份访问?

DynamoDB作为无服务器NoSQL数据库,经常被移动端、单页应用和静态网站直接调用。但这类客户端无法安全保存长期访问密钥,一旦密钥泄露,整个表都可能被拖走。Web Identity Federation(Web身份联合)正是为了解决这个问题而设计:应用先通过外部身份提供商完成用户登录,再拿身份令牌换取一组有时效的AWS临时凭证,最后用临时凭证访问DynamoDB。下面从认证链路开始讲起,逐步比较Cognito与STS两种实现方式。

如何通过Web Identity Federation为DynamoDB实现安全的联合身份访问?

一、联合身份认证的核心链路

理解DynamoDB联合身份,首先要分清两类凭证:长期访问密钥和临时安全凭证。长期密钥通常只应出现在后端服务或IAM用户手里,绝不应该嵌入前端代码。联合身份认证的目标就是让真实用户通过第三方身份提供商登录,利用身份令牌向AWS证明“我是谁”,然后换取有效期通常为一小时的临时凭证。

这条链路的关键角色有三个:外部IdP(如Google、Facebook、Amazon、Apple或企业OIDC服务)、AWS STS(安全令牌服务)以及IAM角色。用户登录IdP后会拿到一个ID Token,通常是JWT格式,里面包含用户标识和过期时间。应用把这个Token交给STS,STS验证Token的真实性和有效期后,调用IAM角色临时凭证接口,返回AccessKeyId、SecretAccessKey和SessionToken。应用随后用这组临时凭证初始化DynamoDB客户端,所有请求的权限边界由该角色策略决定。

// 前端使用Cognito Identity Pool交换临时凭证
AWS.config.update({
  region: 'ap-southeast-1'
});
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: 'ap-southeast-1:12345678-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
  Logins: {
    'accounts.google.com': idToken
  }
});
const dynamodb = new AWS.DynamoDB.DocumentClient();
const params = {
  TableName: 'UserData',
  Key: {
    UserId: userId
  }
};
dynamodb.get(params, function(err, data) {
  if (err) {
    console.error('读取失败', err);
  } else {
    console.log('读取结果', data.Item);
  }
});

可以看到,应用代码里完全不需要接触任何长期密钥。CognitoIdentityCredentials对象会在第一次API调用前自动完成凭证获取和刷新。当临时凭证过期后,SDK会使用缓存的ID Token重新向身份池申请新凭证,整个过程对业务代码透明。

二、用Cognito身份池托管凭证交换

Amazon Cognito是AWS官方推荐的联合身份入口,它把用户池、身份池和身份提供商整合在一起。用户池负责管理用户的注册、登录和令牌签发;身份池负责把IdP令牌映射为AWS凭证。对于DynamoDB场景,通常只需要配置身份池,并为其指定两个IAM角色:一个给已认证用户,一个给未认证访客。已认证角色是权限控制的核心。

要想让联合身份真正安全地访问DynamoDB,光有角色是不够的,还必须用条件键做行级隔离。默认情况下,如果角色直接允许对整张表执行PutItem或GetItem,那么任何一个通过身份池认证的用户都能读写其他用户的数据。解决方法是利用DynamoDB的LeadingKeys条件,把分区键的值限制为当前联合身份的唯一标识。IAM策略变量 cognito-identity.amazonaws.com:sub 就是身份池为每个用户生成的稳定ID。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:UpdateItem",
        "dynamodb:DeleteItem"
      ],
      "Resource": "arn:aws:dynamodb:ap-southeast-1:123456789012:table/UserData",
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:LeadingKeys": [
            "${cognito-identity.amazonaws.com:sub}"
          ]
        }
      }
    }
  ]
}

这个策略生效后,用户发起的DynamoDB请求中只要分区键不是自己的身份ID,就会收到AccessDenied错误。表设计时需要把UserId设置为主键或分区键,并且在与应用交互时始终使用身份池返回的subject值,不能信任前端传来的任意UserId字符串。否则容易绕过行级限制。

Cognito方案的优势在于SDK集成好、凭证自动刷新、无需自行处理STS调用细节。缺点是如果项目需要对接非AWS托管的自定义OIDC提供商,或者希望完全控制凭证交换流程,Cognito身份池的抽象层反而会带来配置复杂度。这时可以使用下一节的STS直连方式。

三、直接调用STS AssumeRoleWithWebIdentity

某些场景下团队并不想引入Cognito用户池和身份池,而是已经有自己的OIDC身份系统,或者需要精确控制每次凭证申请的行为。AWS STS提供了 AssumeRoleWithWebIdentity 接口,允许应用直接拿Web身份令牌换取临时凭证。这种方式需要先在IAM中创建一个OIDC身份提供商,并配置好客户端ID和指纹。

调用流程和Cognito类似,但需要手工管理Token刷新、会话命名和凭证过期。应用代码负责在每次请求或用例开始前检查本地缓存的凭证是否仍然有效,如果过期则重新调用STS。下面的Python示例展示了完整过程,其中 id_token 由外部IdP签发,RoleArn 指向具有DynamoDB访问权限的角色。

import boto3
sts_client = boto3.client('sts')
response = sts_client.assume_role_with_web_identity(
    RoleArn='arn:aws:iam::123456789012:role/WebIdentityDynamoDBRole',
    RoleSessionName='web-identity-session',
    WebIdentityToken=id_token,
    DurationSeconds=3600
)
credentials = response['Credentials']
dynamodb = boto3.resource(
    'dynamodb',
    aws_access_key_id=credentials['AccessKeyId'],
    aws_secret_access_key=credentials['SecretAccessKey'],
    aws_session_token=credentials['SessionToken']
)
table = dynamodb.Table('UserData')
resp = table.get_item(Key={'UserId': user_id})

和Cognito自动刷新不同,这里应用必须保存好WebIdentityToken,并在临时凭证到期前主动重新调用STS。同时要注意,STS返回的SessionToken不能和长期SecretAccessKey混用,否则会导致签名错误。另一个常见问题是角色信任策略中必须显式信任该OIDC提供商,并限制audience为应用自己的客户端ID,否则攻击者可以拿其他应用的ID Token来申请凭证。

四、权限收敛与常见安全误区

联合身份方案最容易出问题的地方不是代码调用,而是IAM角色权限过宽。很多团队为了快速跑通流程,先给角色配了 dynamodb:* 或者整表读写权限,事后又忘记收紧。对生产环境来说,这等同于把整个表暴露给任何能完成登录的用户。正确做法是从最小权限开始,只允许必要的DynamoDB动作,并且使用条件键把资源范围限制到单个分区键。

除了行级隔离,还要关注令牌有效期和混淆代理风险。Web身份令牌通常有效期不长,但临时凭证默认一小时,最长可设置到十二小时。建议前端在令牌将近过期时通过刷新机制获取新ID Token,而不是等API调用失败后再补救。混淆代理是指攻击者诱导某个OIDC令牌被错误地用于另一角色或另一账户,防御方法是角色信任策略中同时校验 cognito-identity.amazonaws.com:aud 或OIDC的audience字段,以及限制 sts:SourceIdentity 条件键。

最后要提一下审计。所有通过联合身份产生的DynamoDB请求都会在CloudTrail中记录,其中包含 userIdentity 信息,可以帮助定位是哪个联合身份执行了操作。如果发现异常读写,应首先检查身份池的未认证角色是否意外开放,以及前端是否把用户输入的UserId直接传给了DynamoDB。联合身份并不是免死金牌,权限边界仍然需要开发者在角色策略和表设计上花足功夫。

广告位 · 文章底部(后台-模块管理替换此区域)