使用EC2 IAM角色生成S3预签名URL上传时遇InvalidAccessKeyId错误
尝试通过预签名URL向私有S3存储桶上传文件时,触发以下错误:
<Error> <Code>InvalidAccessKeyId</Code> <Message>The AWS Access Key Id you provided does not exist in our records.</Message> <AWSAccessKeyId></AWSAccessKeyId> <RequestId></RequestId> <HostId></HostId> </Error>
EC2服务器已关联具备以下权限的IAM角色:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:*", "s3-object-lambda:*", "kms:*" ], "Resource": "*" } ] }
该角色的信任关系配置:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
业务流程
- 前端调用PHP接口获取预签名上传所需参数:
$s3Client = new Aws\S3\S3Client([ 'version' => '2006-03-01', 'region' => $region ]); $formInputs = ['acl' => 'private']; $options = [ ['acl' => 'private'], ['bucket' => $_ENV['AWS_S3_BUCKET']], ['starts-with', '$key', $pth], ]; $expires = '+1 hours'; $postObject = new \Aws\S3\PostObjectV4( $s3Client, $_ENV['AWS_S3_BUCKET'], $formInputs, $options, $expires ); $fattr = $postObject->getFormAttributes(); $finput = $postObject->getFormInputs();
- 前端通过Ajax提交表单到S3:
var fd = new FormData(); fd.append("acl", fp["acl"]); fd.append("key", fp['Path']); fd.append("X-Amz-Credential", fp["X-Amz-Credential"]); fd.append("X-Amz-Algorithm", fp["X-Amz-Algorithm"]); fd.append("X-Amz-Date", fp["X-Amz-Date"]); fd.append("Policy", fp["Policy"]); fd.append("X-Amz-Signature", fp["X-Amz-Signature"]); fd.append('file', file); $.ajax({ url: fp['action'], type: 'post', data: fd, cache: false, contentType: false, processData: false, })...
原硬编码AWS凭证时功能正常,切换为EC2 IAM角色后出现上述错误,以下是解决思路:
验证EC2实例是否正确获取IAM角色临时凭证:
登录EC2实例,执行curl http://169.254.169.254/latest/meta-data/iam/security-credentials/查看是否返回关联的角色名称;再访问http://169.254.169.254/latest/meta-data/iam/security-credentials/[角色名称],确认返回包含AccessKeyId、SecretAccessKey、SessionToken的有效临时凭证。如果无法获取,检查实例是否正确关联IAM角色,或者重启实例刷新凭证。检查AWS PHP SDK是否自动获取IAM角色凭证:
确保PHP SDK版本支持实例角色凭证自动获取(AWS SDK for PHP v3默认支持),代码中不要显式设置credentials参数。如果SDK没有自动获取,可能是实例元数据访问受限,或者SDK配置被覆盖。可以在初始化S3Client时添加'credentials' => new CredentialProvider()强制使用默认凭证链,或者检查环境变量是否残留旧的硬编码凭证(比如AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY)。检查PostObjectV4生成的凭证是否包含SessionToken:
使用IAM角色生成预签名POST表单时,需要在表单参数中包含X-Amz-Security-Token(对应临时凭证的SessionToken)。查看$finput是否包含该字段,如果没有,说明SDK没有正确处理临时凭证,需要手动添加:// 获取当前凭证 $credentials = $s3Client->getCredentials()->wait(); if ($credentials->getSecurityToken()) { $finput['X-Amz-Security-Token'] = $credentials->getSecurityToken(); }前端也需要将该字段添加到FormData中:
fd.append("X-Amz-Security-Token", fp["X-Amz-Security-Token"]);验证S3存储桶的CORS配置:
确保存储桶CORS规则允许POST请求,并且允许携带必要的请求头(包括x-amz-security-token)。典型的CORS配置示例:[ { "AllowedHeaders": ["*"], "AllowedMethods": ["POST"], "AllowedOrigins": ["你的前端域名"], "ExposeHeaders": [] } ]
内容的提问来源于stack exchange,提问作者barneyAgumble

