【问题标题】:Getting access to cognito user attributes from within Lambda从 Lambda 中访问 c​​ognito 用户属性
【发布时间】:2020-09-15 04:42:24
【问题描述】:

我在我的架构中使用 Cognito、API Gateway 和 Lambda 函数。

在客户端上,我使用 AWS Amplify.API 发出请求,并且该请求在到达 API 网关后由 Cognito 授权。如果请求获得授权,则将其传递给 Lambda 函数,在该函数中,我需要访问发出请求的登录用户,以便能够运行我的业务逻辑。

在 Lambda 函数的上下文中,我可以访问一些环境变量,CognitoUserPoolId 就是其中之一。

我还可以访问通过 API 传递的任何请求,位于 event

{
  "tok": {
    "resource": "/some_resource",
    "path": "/some_resource",
    "httpMethod": "GET",
    "headers": {
      "Accept": "application/json",
      "Accept-Encoding": "gzip, deflate, br",
      "Accept-Language": "en-GB,en;q=0.9,sv;q=0.8,en-US;q=0.7,el;q=0.6,de;q=0.5,el-GR;q=0.4",
      "CloudFront-Forwarded-Proto": "https",
      "CloudFront-Is-Desktop-Viewer": "true",
      "CloudFront-Is-Mobile-Viewer": "false",
      "CloudFront-Is-SmartTV-Viewer": "false",
      "CloudFront-Is-Tablet-Viewer": "false",
      "CloudFront-Viewer-Country": "SE",
      "content-type": "application/json",
      "Host": "XXXXXX.execute-api.eu-central-1.amazonaws.com",
      "origin": "http://localhost:8080",
      "Referer": "http://localhost:8080/some_resource",
      "sec-fetch-dest": "empty",
      "sec-fetch-mode": "cors",
      "sec-fetch-site": "cross-site",
      "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
      "Via": "2.0 XXXXXXXXXXXX.cloudfront.net (CloudFront)",
      "X-Amz-Cf-Id": "XXXXXXXXXXXXXXXXXXXXXXXXTelrg==",
      "x-amz-date": "20200527T233945Z",
      "x-amz-security-token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX8fR",
      "X-Amzn-Trace-Id": "Root=1-5XXXXa41-730XXXXXXXXXXabe42f1",
      "X-Forwarded-For": "XXX.XXX.XX.XXX, XXX.XXX.XXX.XXX",
      "X-Forwarded-Port": "443",
      "X-Forwarded-Proto": "https"
    },
    "multiValueHeaders": {
      "Accept": [
        "application/json"
      ],
      "Accept-Encoding": [
        "gzip, deflate, br"
      ],
      "Accept-Language": [
        "en-GB,en;q=0.9,sv;q=0.8,en-US;q=0.7,el;q=0.6,de;q=0.5,el-GR;q=0.4"
      ],
      "CloudFront-Forwarded-Proto": [
        "https"
      ],
      "CloudFront-Is-Desktop-Viewer": [
        "true"
      ],
      "CloudFront-Is-Mobile-Viewer": [
        "false"
      ],
      "CloudFront-Is-SmartTV-Viewer": [
        "false"
      ],
      "CloudFront-Is-Tablet-Viewer": [
        "false"
      ],
      "CloudFront-Viewer-Country": [
        "SE"
      ],
      "content-type": [
        "application/json"
      ],
      "Host": [
        "XXXXXX.execute-api.eu-central-1.amazonaws.com"
      ],
      "origin": [
        "http://localhost:8080"
      ],
      "Referer": [
        "http://localhost:8080/some_resource"
      ],
      "sec-fetch-dest": [
        "empty"
      ],
      "sec-fetch-mode": [
        "cors"
      ],
      "sec-fetch-site": [
        "cross-site"
      ],
      "User-Agent": [
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36"
      ],
      "Via": [
        "2.0 XXXXXXXXXXXX.cloudfront.net (CloudFront)"
      ],
      "X-Amz-Cf-Id": [
        "XXXXXXXXXXXXXXXXXXXXXXXXTelrg=="
      ],
      "x-amz-date": [
        "20200527T233945Z"
      ],
      "x-amz-security-token": [
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX8fR"
      ],
      "X-Amzn-Trace-Id": [
        "Root=1-5XXXXa41-730XXXXXXXXXXabe42f1"
      ],
      "X-Forwarded-For": [
        "XXX.XXX.XX.XXX, XXX.XXX.XXX.XXX"
      ],
      "X-Forwarded-Port": [
        "443"
      ],
      "X-Forwarded-Proto": [
        "https"
      ]
    },
    "queryStringParameters": null,
    "multiValueQueryStringParameters": null,
    "pathParameters": null,
    "stageVariables": null,
    "requestContext": {
      "resourceId": "5qXXXXXXX",
      "resourcePath": "/some_resource",
      "httpMethod": "GET",
      "extendedRequestId": "NNwKXXXXXXXXX=",
      "requestTime": "27/May/2020:23:39:45 +0000",
      "path": "/dev/some_resource",
      "accountId": "18XXXXXXXX",
      "protocol": "HTTP/1.1",
      "stage": "dev",
      "domainPrefix": "XXXXXX",
      "requestTimeEpoch": 1590622785474,
      "requestId": "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
      "identity": {
        "cognitoIdentityPoolId": "eu-central-1:XXXXXX-YYY-YYYY-YYYY-YYYYY",
        "accountId": "181606720624",
        "cognitoIdentityId": "eu-central-1:XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
        "caller": "SOME_STRING_HERE:CognitoIdentityCredentials",
        "sourceIp": "XXX.XXX.XXX.XXX",
        "principalOrgId": null,
        "accessKey": "ACCESS_KEY_HERE",
        "cognitoAuthenticationType": "authenticated",
        "cognitoAuthenticationProvider": "cognito-idp.eu-central-1.amazonaws.com/eu-central-1_XXXXXX,cognito-idp.eu-central-1.amazonaws.com/eu-central-1_XXXXXX:CognitoSignIn:XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
        "userArn": "arn:aws:sts::XXXXXX:assumed-role/amplify-XXXXXX-dev-XXXXXX-authRole/CognitoIdentityCredentials",
        "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
        "user": "SOME_STRING_HERE:CognitoIdentityCredentials"
      },
      "domainName": "XXXXXX.execute-api.eu-central-1.amazonaws.com",
      "apiId": "XXXXXX"
    },
    "body": null,
    "isBase64Encoded": false
  }
}

有了所有这些信息,我如何访问发出请求的用户的用户属性?

我一直在研究: Boto3 CognitoIdentityProvider.get_user()Boto3 CognitoIdentityProvider.admin_get_user(),但是两者都需要 AccessTokenuserId,并且在 Lambda 函数的上下文中都不可用。我想到的唯一方法是在请求的有效负载中传递一些额外的信息,但这似乎并不是真正检索用户属性的最佳方法。

编辑

我没有在 API 上使用自定义授权方。在使用 Amplify 设置 API 时,我选择了受保护的路径,并且所有请求都针对 Cognito 用户池进行了身份验证/授权。在amplify push之后我可以看到部署的API在所有资源的Method Request中Auth下有AWS_IAM

Amplify.API.get 发出请求时包含用于授权的标头,更具体地说:

:authority: XXXXXX.execute-api.eu-central-1.amazonaws.com
:method: GET
:path: /dev/some_resource
:scheme: https
accept: application/json
accept-encoding: gzip, deflate, br
accept-language: en-GB,en;q=0.9,sv;q=0.8,en-US;q=0.7,el;q=0.6,de;q=0.5,el-GR;q=0.4
authorization: AWS4-HMAC-SHA256 Credential=ASIAXXXXXXXX/20200528/eu-central-1/execute-api/aws4_request, SignedHeaders=accept;content-type;host;x-amz-date;x-amz-security-token, Signature=b6a7aXXX1c447bXXX...XXX
content-type: application/json
origin: http://localhost:8080
referer: http://localhost:8080/dashboard
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36
x-amz-date: 20200528T075958Z
x-amz-security-token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX...XXXXXXXXXX

API 资源通过 Lambda 代理集成集成到 Lambda 函数中。我可以看到正在调用 Lambda 函数,因此请求已被授权。但是,我在 Lambda 中的 event 中所拥有的只是我在上面第一个剪辑中粘贴的内容。

鉴于这是一个代理集成,我希望 API 上下文中的所有内容都应该转发到 API 后面的 Lambda。但是,我看不到如何使用我在 Lambda 中提供的任何信息来访问 user_attributes。有什么指点吗?

编辑 2

我现在正在研究使用自定义标头从客户端传递与用户相关的数据,可能类似于:

'x-user-sub': '58ce94f6-53vd-4s3e-b088-cd6f85s0ff43'

然后在我的 Lambda 函数中使用以下内容来获取用户属性:

  c_client = boto3.client('cognito-idp')
  response = c_client.admin_get_user(
    UserPoolId='eu-central-1_XXXXXX',
    Username='58ce94f6-53vd-4s3e-b088-cd6f85s0ff43'
  )

以上返回:

{
  'Username': '58ce94f6-53vd-4s3e-b088-cd6f85s0ff43',
  'UserAttributes': [
    {
      'Name': 'sub',
      'Value': '58ce94f6-53vd-4s3e-b088-cd6f85s0ff43'
    },
    {
      'Name': 'email_verified',
      'Value': 'true'
    },
    {
      'Name': 'phone_number_verified',
      'Value': 'true'
    },
    {
      'Name': 'phone_number',
      'Value': '+46XXXXXXXXXX'
    },
    {
      'Name': 'email',
      'Value': 'XXXXXX.XXXXX@YYYY.com'
    }
  ],
  'UserCreateDate': datetime.datetime(2020, 5, 14, 15, 43, 3, 370000, tzinfo=tzlocal()),
  'UserLastModifiedDate': datetime.datetime(2020, 5, 15, 16, 32, 43, 424000, tzinfo=tzlocal()),
  'Enabled': true,
  'UserStatus': 'CONFIRMED',
  'ResponseMetadata': {
    'RequestId': 'e441edd4-XXX-46ba-XXX-922691471f2c',
    'HTTPStatusCode': 200,
    'HTTPHeaders': {
      'date': 'Thu, 28 May 2020 12:58:18 GMT',
      'content-type': 'application/x-amz-json-1.1',
      'content-length': '431',
      'connection': 'keep-alive',
      'x-amzn-requestid': 'e441edd4-XXX-46ba-XXX-922691471f2c'
    },
    'RetryAttempts': 0
  }
}

这个解决方案有什么明显的我遗漏的吗?我是否通过在客户标头中传递 sub 来引入任何安全风险?

非常感谢任何帮助。

【问题讨论】:

  • 在您的映射模板中 $context.authorizer.claims.sub == userId
  • @pkarfs 感谢您的评论。您是否建议我将其从 API Gateway 转发到 Lambda?如果是这样,您是否有机会提供一个如何做到这一点的指针?也许一些链接?最后,userId 是 cognito 用户的用户名还是如何使用该变量的值?
  • 不用担心,首先您的事件 requestContext 没有 authorizer.claims 密钥对象,我建议这意味着您实际上没有启用/需要授权器(或忘记部署您的 rest api) .如果您使用 Lambda 作为代理,您可以将值转发到您的 lambda,或者如果您使用 lambda 作为服务,则可以通过集成映射模板转发该值。最后 sub 是在 cognito 中分配给您的用户的唯一值。如果未指定用户名,则是 sub = 用户名。
  • 感谢 pkarfs。我使用的是 AWS_IAM 授权,而不是自定义授权方,不确定这是否与 requestContext 中没有授权方对象的原因有关。我添加了更多信息来描述流程。另外,我在 API 和它背后的 Lambda 函数之间使用了 Lambda 代理集成,这是否意味着 API 应该自动将与请求相关的所有信息转发给 Lambda 函数?
  • 您不应该在标头中传递 sub,令牌授权的全部意义在于避免这样做。另外,如果您使用 cognito 为什么要使用 AWS_IAM 授权?将其更改为您的认知用户池。这就是您没有收到用户上下文响应的原因。

标签: amazon-web-services aws-lambda aws-api-gateway amazon-cognito aws-amplify-cli


【解决方案1】:

我认为这篇 here 的帖子可能会对您有所帮助。

特别是这部分:

API 网关的 Cognito 用户池授权者

API Gateway 最近推出了对 Cognito 用户池的支持 授权人。如果您使用 Cognito 用户池授权器,则不需要 设置您自己的自定义授权方来验证令牌。一旦你的 API 方法配置了 Cognito User Pool Authorizer,你可以通过 API 方法的授权标头中未过期的 ID 令牌。如果 它是您的用户池用户的有效 ID 令牌,然后您可以 使用您的 API 访问 ID Token 的所有声明 '$context.authorizer.claims'。

例如 ‘$context.authorizer.claims.email’ 将返回用户的电子邮件地址 和'$context.authorizer.claims.sub' 将返回您用户的唯一 标识符。如果 ID 令牌已过期或无效,则 Cognito 用户 Pool Authorizer 将向调用者发送 Unauthorized (401) 响应。

Here有一些关于如何覆盖apigateway请求/响应参数的示例

Here您可以看到如何设置集成以及将哪些数据传递到 API 网关。

值得检查 data mappingaccess logging 的参考

【讨论】:

  • 感谢您的回复!我对是否使用 Cognito 用户池授权者不是很有信心。我已经使用 amplify/cli 部署了 API,当我查看 AWS API Gateway 控制台时,我在方法请求的“授权”下看到“AWS_IAM”。也许这是一个较新的授权流程?同时,我有一个 Lambda 代理集成,是不是所有的请求数据都应该包含在 event 中?是否无法使用 event 中我可用的任何属性来检索给定会话的 Cognito 用户?
猜你喜欢
  • 2018-08-11
  • 1970-01-01
  • 2019-07-22
  • 1970-01-01
  • 1970-01-01
  • 2011-02-18
  • 2019-04-18
  • 2018-10-25
  • 2021-04-04
相关资源
最近更新 更多