尝试从 Lambda 获取图像,但访问被拒绝。使用 Amplify 客户端库效果很好
Trying to get Image from Lambda but I get Access Denied. With the Amplify client library works well
我使用无服务器框架创建了 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
这些角色有这些规则:
CognitoAuthorizedRole:
{
"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 和放大库调用 object,我可以毫无问题地获取图像。
但是,如果我在我的 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 returns 我 URL 但是当我从客户端得到这个时,我得到一个访问被拒绝的错误。
MyLambda:
handler: functions/My_Lambda.handler
environment:
BUCKET: !Ref AssetsBucket
iamRoleStatements:
- Effect: Allow
Action: s3:GetObject
Resource: !GetAtt AssetsBucket.Arn
我不知道为什么会出现这个错误...对于客户端,该库运行良好,但如果我从 Lambda 尝试类似的东西,它就不起作用...
这里是关于 headers 的更多信息:
HEADERS 来自正确的图像(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
HEADERS 来自图像的 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
两者headers的某些部分不同...例如,LAMBDA headers没有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
效果很好。
然后,如果我转到网页并使用来自 Amplify 的方法 Auth.SignIn
以用户身份登录,然后执行 Storage.get:
同样,效果很好。
但是,如果我调用 lambda,现在 url 会发生变化:
现在这个 url 有更多的参数...
这是我正在做的一个例子:https://github.com/MontoyaAndres/test_problem_s3_cognito
我对您对现有系统的解释感到困惑(抱歉!),但一般方法是以下之一:
使用 Cognito
您的后端可以使用 Cognito 对用户进行身份验证,然后使用 AssumeRoleWithWebIdentity
到 return 一组凭据。然后,用户的客户端可以根据分配的权限使用这些凭据直接访问 AWS 服务。
例如,他们可能被允许访问 Amazon S3 存储桶中自己的子目录,或从特定的 DynamoDB table 中读取。这可以通过直接向 AWS 发送请求而不是通过后端来完成。
使用预签名URLs
如果您的目标纯粹是授予对 Amazon S3 中私有对象的访问权限,那么而不是使用 Cognito,您的后端可以生成提供时间的 Amazon S3 pre-signed URLs - 对私有对象的访问受限。
每当后端生成包含对私有对象的引用的页面时(例如通过 <img src=...>
标记),它可以执行以下操作:
- 应用程序通过检查应用程序数据库中的信息来验证用户是否有权访问私有对象
- 如果用户有权访问私有对象,后端会生成预签名URL
- 预签名 URL 在 HTML 页面中 return 编辑(甚至直接 link)
- 当 S3 收到预签名的 URL 时,它验证签名,如果正确,returns 私有对象
这种方法的好处是应用程序可以确定对单个对象的细粒度访问,而不是简单地使用存储桶和前缀来定义访问。这在用户之间共享数据的情况下非常有用(例如,用户可以与其他用户共享照片的照片共享应用程序)基于每个对象。
不要混用
在查看您的代码示例时,您的 Cognito 角色似乎正在授予对 S3 存储桶特定部分的访问权限:
arn:aws:s3:::MY_BUCKET_HERE/protected/${cognito-identity.amazonaws.com:sub}/*
然后客户端可以使用他们与 Cognito 相关的凭据直接访问存储桶的那部分。不需要生成预签名的URLs.
我使用无服务器框架创建了 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
这些角色有这些规则:
CognitoAuthorizedRole:
{
"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 和放大库调用 object,我可以毫无问题地获取图像。
但是,如果我在我的 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 returns 我 URL 但是当我从客户端得到这个时,我得到一个访问被拒绝的错误。
MyLambda:
handler: functions/My_Lambda.handler
environment:
BUCKET: !Ref AssetsBucket
iamRoleStatements:
- Effect: Allow
Action: s3:GetObject
Resource: !GetAtt AssetsBucket.Arn
我不知道为什么会出现这个错误...对于客户端,该库运行良好,但如果我从 Lambda 尝试类似的东西,它就不起作用...
这里是关于 headers 的更多信息:
HEADERS 来自正确的图像(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
HEADERS 来自图像的 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
两者headers的某些部分不同...例如,LAMBDA headers没有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
效果很好。
然后,如果我转到网页并使用来自 Amplify 的方法 Auth.SignIn
以用户身份登录,然后执行 Storage.get:
同样,效果很好。
但是,如果我调用 lambda,现在 url 会发生变化:
现在这个 url 有更多的参数...
这是我正在做的一个例子:https://github.com/MontoyaAndres/test_problem_s3_cognito
我对您对现有系统的解释感到困惑(抱歉!),但一般方法是以下之一:
使用 Cognito
您的后端可以使用 Cognito 对用户进行身份验证,然后使用 AssumeRoleWithWebIdentity
到 return 一组凭据。然后,用户的客户端可以根据分配的权限使用这些凭据直接访问 AWS 服务。
例如,他们可能被允许访问 Amazon S3 存储桶中自己的子目录,或从特定的 DynamoDB table 中读取。这可以通过直接向 AWS 发送请求而不是通过后端来完成。
使用预签名URLs
如果您的目标纯粹是授予对 Amazon S3 中私有对象的访问权限,那么而不是使用 Cognito,您的后端可以生成提供时间的 Amazon S3 pre-signed URLs - 对私有对象的访问受限。
每当后端生成包含对私有对象的引用的页面时(例如通过 <img src=...>
标记),它可以执行以下操作:
- 应用程序通过检查应用程序数据库中的信息来验证用户是否有权访问私有对象
- 如果用户有权访问私有对象,后端会生成预签名URL
- 预签名 URL 在 HTML 页面中 return 编辑(甚至直接 link)
- 当 S3 收到预签名的 URL 时,它验证签名,如果正确,returns 私有对象
这种方法的好处是应用程序可以确定对单个对象的细粒度访问,而不是简单地使用存储桶和前缀来定义访问。这在用户之间共享数据的情况下非常有用(例如,用户可以与其他用户共享照片的照片共享应用程序)基于每个对象。
不要混用
在查看您的代码示例时,您的 Cognito 角色似乎正在授予对 S3 存储桶特定部分的访问权限:
arn:aws:s3:::MY_BUCKET_HERE/protected/${cognito-identity.amazonaws.com:sub}/*
然后客户端可以使用他们与 Cognito 相关的凭据直接访问存储桶的那部分。不需要生成预签名的URLs.