amazon-s3 - 试图从 Lambda 获取图像,但我被拒绝访问。使用 Amplify 客户端库运行良好
问题描述
我已经使用无服务器框架创建了 S3 存储桶,如下所示:
AssetsBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
CorsConfiguration:
CorsRules:
- AllowedMethods:
- GET
- HEAD
- PUT
AllowedOrigins:
- '*'
AllowedHeaders:
- '*'
ExposedHeaders:
- 'x-amz-server-side-encryption'
- 'x-amz-request-id'
- 'x-amz-id-2'
- 'ETag'
MaxAge: 3000
然后,我创建了一个身份池并定义了我需要的角色:
IdentityPool:
Type: AWS::Cognito::IdentityPool
Properties:
AllowUnauthenticatedIdentities: true
CognitoIdentityProviders:
- ClientId: !Ref WebUserPoolClient
ProviderName: !GetAtt CognitoUserPool.ProviderName
CognitoAuthorizedRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Federated: "cognito-identity.amazonaws.com"
Action:
- sts:AssumeRoleWithWebIdentity
Condition:
StringEquals:
"cognito-identity.amazonaws.com:aud": !Ref IdentityPool
ForAnyValue:StringLike:
"cognito-identity.amazonaws.com:amr": authenticated
CognitoUnAuthorizedRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Federated: "cognito-identity.amazonaws.com"
Action:
- sts:AssumeRoleWithWebIdentity
Condition:
StringEquals:
"cognito-identity.amazonaws.com:aud": !Ref IdentityPool
ForAnyValue:StringLike:
"cognito-identity.amazonaws.com:amr": unauthenticated
IdentityPoolRoleMapping:
Type: AWS::Cognito::IdentityPoolRoleAttachment
Properties:
IdentityPoolId: !Ref IdentityPool
Roles:
authenticated: !GetAtt CognitoAuthorizedRole.Arn
unauthenticated: !GetAtt CognitoUnAuthorizedRole.Arn
这些角色具有以下规则:
CognitoAuthorized角色:
{
"Version": "2012-10-17",
"Statement": [
{
"Condition": {
"StringLike": {
"s3:prefix": [
"public/",
"public/*",
"protected/",
"protected/*",
"private/${cognito-identity.amazonaws.com:sub}/",
"private/${cognito-identity.amazonaws.com:sub}/*"
]
}
},
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::MY_BUCKET_HERE"
],
"Effect": "Allow"
},
{
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::MY_BUCKET_HERE/uploads/*",
"arn:aws:s3:::MY_BUCKET_HERE/public/*",
"arn:aws:s3:::MY_BUCKET_HERE/protected/${cognito-identity.amazonaws.com:sub}/*",
"arn:aws:s3:::MY_BUCKET_HERE/private/${cognito-identity.amazonaws.com:sub}/*"
],
"Effect": "Allow"
},
{
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::MY_BUCKET_HERE/protected/*"
],
"Effect": "Allow"
}
]
}
CognitoUnAuthorizedRole:
{
"Version": "2012-10-17",
"Statement": [
{
"Condition": {
"StringLike": {
"s3:prefix": [
"public/",
"public/*",
"protected/",
"protected/*"
]
}
},
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::MY_BUCKET_HERE"
],
"Effect": "Allow"
},
{
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::MY_BUCKET_HERE/public/*",
"arn:aws:s3:::MY_BUCKET_HERE/protected/*"
],
"Effect": "Allow"
}
]
}
所以,这是我的问题:
如果我使用放大库的 Storage.get 方法调用一个对象,我可以毫无问题地获得图像。
但是,如果我在我的 LAMBDA 中这样做:
const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
const s3Client = new S3Client();
const command = new GetObjectCommand(params);
const url = await getSignedUrl(s3Client, command, { expiresIn: 3600 });
return url;
lambda 向我返回了 URL,但是当我从客户端获取这个 URL 时,我收到了拒绝访问错误。
MyLambda:
handler: functions/My_Lambda.handler
environment:
BUCKET: !Ref AssetsBucket
iamRoleStatements:
- Effect: Allow
Action: s3:GetObject
Resource: !GetAtt AssetsBucket.Arn
我不知道为什么会出现此错误...对于客户端,该库运行良好,但是如果我尝试从 Lambda 进行类似的操作,它将无法正常工作...
以下是有关标头的更多信息:
来自正确图像的标题(Storage.get 方法):
Response-headers:
HTTP/1.1 200 OK
x-amz-id-2: HERE_THE_VALUE
x-amz-request-id: HERE_THE_VALUE
Date: Sat, 17 Jul 2021 21:10:06 GMT
Last-Modified: Sun, 11 Jul 2021 19:28:42 GMT
ETag: "HERE_THE_VALUE"
Accept-Ranges: bytes
Content-Type: application/octet-stream
Server: AmazonS3
Content-Length: 1338
Request-headers:
GET /public/menu_icons/ADMINISTRADOR.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIA4YRKNIM2VGLZX7UR%2F20210717%2Fus-east-2%2Fs3%2Faws4_request&X-Amz-Date=20210717T211004Z&X-Amz-Expires=900&X-Amz-Security-Token=IQoJ... HTTP/1.1
Host: ecommerce-gateway-develop-assetsbucket-72ahiv6louu0.s3.us-east-2.amazonaws.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Sec-GPC: 1
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: image
Referer: http://localhost:3000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
来自 LAMBDA 图像错误的标题:
Response-headers:
HTTP/1.1 403 Forbidden
x-amz-request-id: HERE_THE_VALUE
x-amz-id-2: HERE_THE_VALUE
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Sat, 17 Jul 2021 21:10:04 GMT
Server: AmazonS3
Request-headers:
GET /public/menu_icons/ADMINISTRADOR.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIA4YRKNIM2VU4GBIEB%2F20210717%2Fus-east-2%2Fs3%2Faws4_request&X-Amz-Date=20210717T211003Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJ.... HTTP/1.1
Host: ecommerce-gateway-develop-assetsbucket-72ahiv6louu0.s3.us-east-2.amazonaws.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Sec-GPC: 1
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: image
Referer: http://localhost:3000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
两个标头的某些部分不同...例如,LAMBDA 标头没有 ETag... 我该怎么办?谢谢!
新更新
如果我从 lambda 的日志中获取 url,我会得到:
https://ecommerce-gateway-develop-assetsbucket-72ahiv6louu0.s3.us-east-2.amazonaws.com/public/menu_icons/REPORTES.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=xxxxxxIM22YARUSUJ%2F2xxxxx17%2Fus-east-2%2Fs3%2Faws4_request&X-Amz-Date=20210717T230538Z&X-Amz-Expires=3600&X-Amz-Signature=0a37f13e2a7axxxxf4d38cebxxxxxxxxx557c8805057ac6ddffe71c&X-Amz-SignedHeaders=host&x-id=GetObject
效果很好。
然后,如果我访问网页并使用Auth.SignIn
Amplify 中的方法以用户身份登录,然后执行 Storage.get:
同样,效果很好。
但是,如果我调用 lambda,现在 url 会改变:
现在这个网址有更多的参数......
这是我正在做的一个例子:https ://github.com/MontoyaAndres/test_problem_s3_cognito
解决方案
我对您对现有系统的解释感到困惑(对不起!),但一般方法是以下之一:
使用认知
您的后端可以使用 Cognito 对用户进行身份验证,然后用于AssumeRoleWithWebIdentity
返回一组凭据。然后,用户的客户端可以使用这些凭证根据分配的权限直接访问 AWS 服务。
例如,他们可能被允许访问他们自己在 Amazon S3 存储桶中的子目录,或者从特定的 DynamoDB 表中读取。这可以通过直接向 AWS 发送请求而不是通过后端来完成。
使用预签名 URL
如果您的目标纯粹是授予对 Amazon S3 中私有对象的访问权限,那么您的后端可以生成Amazon S3 预签名 URL ,而不是使用 Cognito ,这些 URL提供对私有对象的限时访问。
每当后端生成一个包含对私有对象的引用(例如通过<img src=...>
标签)的页面时,它可以执行以下操作:
- 应用程序通过检查应用程序数据库中的信息来验证用户是否有权访问私有对象
- 如果用户有权访问私有对象,后端会生成一个预签名的 URL
- 预签名的 URL 在 HTML 页面中返回(甚至作为直接链接)
- 当 S3 收到预签名的 URL 时,它会验证签名,如果正确,则返回私有对象
这种方法的好处是应用程序可以确定对单个对象的细粒度访问,而不是简单地使用存储桶和前缀来定义访问。这在用户之间共享数据的情况下非常有用(例如,用户可以与其他用户共享照片的照片共享应用程序)基于每个对象。
不要混合
在查看您的代码示例时,您的 Cognito 角色似乎正在授予对 S3 存储桶特定部分的访问权限:
arn:aws:s3:::MY_BUCKET_HERE/protected/${cognito-identity.amazonaws.com:sub}/*
然后,客户端可以使用其与 Cognito 相关的凭证直接访问存储桶的该部分。无需生成预签名 URL。
推荐阅读
- c# - 我需要从确定性构建中存储 PDB 文件吗?
- python - 展开并将 1D numpy 数组复制到 3D
- video - 为什么ffmpeg输出以0001而不是0000开头的帧?
- google-apps-script - Google Scripts:将 Sheets 表添加到 Google Doc
- pyspark - databricks-connect, py4j.protocol.Py4JJavaError: 调用 o342.cache 时出错
- sql - 给定日期属于哪个财政年度季度?
- azure - B2C 自定义电子邮件验证:告诉用户他们需要做什么
- groovy - groovy spock 用 spy 测试闭包
- java - IntelliJ 和 Gradlew:使 ShadowJar 成为默认构建目标
- javascript - 如何将某个计算的 jQuery 结果显示为“不可用”而不是显示 £0