尝试从 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:

https://ecommerce-gateway-develop-assetsbucket-72ahiv6louu0.s3.us-east-2.amazonaws.com/public/menu_icons/ADMINISTRADOR.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIA4YxxxxxxxxxxF20210717%2Fus-east-2%2Fs3%2Faws4_request&X-Amz-Date=20210717T230825Z&X-Amz-Expires=900&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEG8aCXVzLWVhc3QtMiJHMEUCIE3FrI0oFvpEIxxxxxxxxdm1bc&X-Amz-Signature=716642f08xxxxxxxxxx988e954b0335a2e04df44e7&X-Amz-SignedHeaders=host&x-amz-user-agent=aws-sdk-js%2F3.6.1%20os%2FLinux%20lang%2Fjs%20md%2Fbrowser%2FChrome_91.0.4472.124%20api%2Fs3%2F3.6.1%20aws-amplify%2F4.1.3_js&x-id=GetObject

同样,效果很好。

但是,如果我调用 lambda,现在 url 会发生变化:

https://ecommerce-gateway-develop-assetsbucket-72ahiv6louu0.s3.us-east-2.amazonaws.com/public/menu_icons/ADMINISTRADOR.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIA4xxxxxxxxxNGRZ7U%2F20210717%2Fus-east-2%2Fs3%2Faws4_request&X-Amz-Date=20210717T230824Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxtZmeLenE30kHskEkfe4%3D&X-Amz-Signature=b9a400dxxxxxxxxx047445657919ff3b993b893fcbb3&X-Amz-SignedHeaders=host&x-id=GetObject

现在这个 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.