带有 Cognito 的 AWS Lambda API 网关 - 如何使用 IdentityId 访问和更新 UserPool 属性?
Posted
技术标签:
【中文标题】带有 Cognito 的 AWS Lambda API 网关 - 如何使用 IdentityId 访问和更新 UserPool 属性?【英文标题】:AWS Lambda API gateway with Cognito - how to use IdentityId to access and update UserPool attributes? 【发布时间】:2017-07-12 04:32:06 【问题描述】:好的,我现在已经进入这个阶段并取得了重大进展,但仍然对基本面感到困惑。
我的应用程序使用 Cognito 用户池来创建和管理用户 - 这些用户池在 S3 上通过其 IdentityId 进行标识。我的每个用户都有自己的 S3 文件夹,AWS 自动给他们一个与用户的 IdentityId 相同的文件夹名称。
我需要将 IdentityId 与其他 Cognito 用户信息相关联,但不知道如何操作。
我需要的关键是能够识别给定 IdentityId 的用户名以及其他认知用户属性 - 这非常困难。
因此,第一场战斗是弄清楚当 Cognito 用户通过 AWS API Gateway 发出请求时如何获取 IdentityId。最后我解决了这个问题,现在我有一个 Cognito 用户,他向 API Gateway 发出请求,而我背后的 Lambda 函数现在有了 IdentityId。这一点有效。
但我完全不知道现在如何访问存储在用户池中的 Cognito 用户信息。我找不到任何明确的信息,当然也没有代码,说明如何使用 IdentityId 来获取 Cognito 用户的属性、用户名等。
看来,如果我在 API Gateway 中使用“Cognito 用户池”来授权我的方法,那么可以使用正文映射模板将 Cognito 用户信息(例如 sub 和用户名和电子邮件地址)放入上下文中,但我没有得到 IdentityId。
但是,如果我使用 AWS_IAM
在 API 网关中授权我的方法,那么正文映射模板会执行相反的操作 - 它为我提供 IdentityId,但不提供 Cognito 用户字段,例如 sub、用户名和电子邮件。
这让我发疯了——如何将 IdentityId 和所有 Cognito 用户字段和属性整合到一个数据结构中?我似乎只能得到其中一个的事实是没有意义的。
【问题讨论】:
【参考方案1】:事实证明,要使用 AWS Lambda/Cognito/API Gateway 同时获取 IdentityId 和用户详细信息,您需要有一个使用 AWS_IAM
(不是 COGNITO_USER_POOLS
)进行身份验证的 Lambda 函数,您必须将您的请求发送到 AWS API Gateway,但它必须是签名请求,然后您必须修改集成请求正文映射模板,以便在事件中为您提供 IdentityId(可能是上下文?不记得了)。现在您有了 IdentityId。呸。现在您必须将客户端的 Cognito ID 令牌从前端提交到后端。验证令牌很重要——如果你不验证它,你就不能相信它没有被篡改。要解码和验证令牌,您必须从用户池中获取密钥,将它们放入脚本中,确保您的 AWS lambda zip 文件中包含 jwt 解码库和签名验证库。现在您的脚本必须验证从前端提交的令牌,然后您可以从令牌中获取用户详细信息。瞧!现在您拥有 IdentityId 和用户详细信息,例如他们的子用户、用户名和电子邮件地址。很简单。
以上是使用 AWS Cognito/Lambda/API Gateway 获取与 IdentityId 关联的用户名所需的内容。这花了我几天的时间才能开始工作。
我能否对任何徘徊于此的亚马逊员工说......你需要解决这个问题。这让我很生气,因为这太难了,而且浪费了我这么多时间。
解决办法:
我通过在此处修改亚马逊员工的自定义授权人来做到这一点: https://s3.amazonaws.com/cup-resources/cup_custom_authorizer_lambda_function_blueprint.zip
在此处找到并描述: https://aws.amazon.com/blogs/mobile/integrating-amazon-cognito-user-pools-with-api-gateway/
use strict';
let util = require('util');
var jwt = require('jsonwebtoken');
var jwkToPem = require('jwk-to-pem');
var userPoolId = 'YOUR USERPOOL ID';
var region = 'YOUR REGION'; //e.g. us-east-1
var iss = 'https://cognito-idp.' + region + '.amazonaws.com/' + userPoolId;
//https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html
// DOWNLOAD FROM https://cognito-idp.region.amazonaws.com/userPoolId/.well-known/jwks.json
let userPoolKeys = PUT YOUR DOWNLOADED USER POOL KEYS JSON HERE;
var pems = ;
let convertKeysToPems = () =>
var keys = userPoolKeys['keys'];
for(var i = 0; i < keys.length; i++)
//Convert each key to PEM
var key_id = keys[i].kid;
var modulus = keys[i].n;
var exponent = keys[i].e;
var key_type = keys[i].kty;
var jwk = kty: key_type, n: modulus, e: exponent;
var pem = jwkToPem(jwk);
pems[key_id] = pem;
exports.handler = function(event, context)
convertKeysToPems()
console.log(event);
let token = event['body-json'].cognitoUserToken;
console.log(event['body-json'].cognitoUserToken);
ValidateToken(pems, event, context, token);
;
let ValidateToken = (pems, event, context, token) =>
//Fail if the token is not jwt
var decodedJwt = jwt.decode(token, complete: true);
console.log(decodedJwt)
if (!decodedJwt)
console.log("Not a valid JWT token");
context.fail("Unauthorized");
return;
//Fail if token is not from your UserPool
if (decodedJwt.payload.iss != iss)
console.log("invalid issuer");
context.fail("Unauthorized");
return;
//Reject the jwt if it's not an 'Access Token'
if (decodedJwt.payload.token_use != 'id')
console.log("Not an id token");
context.fail("Unauthorized");
return;
//Get the kid from the token and retrieve corresponding PEM
var kid = decodedJwt.header.kid;
var pem = pems[kid];
if (!pem)
console.log(pems, 'pems');
console.log(kid, 'kid');
console.log('Invalid token');
context.fail("Unauthorized");
return;
//Verify the signature of the JWT token to ensure it's really coming from your User Pool
jwt.verify(token, pem, issuer: iss , function(err, payload)
if(err)
context.fail("Unauthorized");
else
let x = decodedJwt.payload
x.identityId = context.identity.cognitoIdentityId
//let x = 'identityId': context['cognito-identity-id'], 'decodedJwt': decodedJwt
console.log(x);
context.succeed(x);
);
【讨论】:
我同意你的观点,你有没有机会尝试创建与提供的 sub 匹配的 IAM S3 策略? AWS 让这件事变得如此困难,但仍然没有为问题提供真正的解决方案,这真是令人发指。 仍在挖掘这个 多年过去了,这仍然是一个问题。不敢相信。 AWS,您是不是太老了,行动不便?【参考方案2】:如果我理解正确,您希望 CognitoIdentityId
和 User 属性在同一个位置。我们如何做到的,是这样的:
从事件请求上下文中我们得到 IdentityId:
event.requestContext.identity.cognitoIdentityId
同样从请求上下文中我们得到用户的 sub:
event.requestContext.identity.cognitoAuthenticationProvider.split(':CognitoSignIn:')[1]
然后,您可以使用 sub 请求其余属性,方法如下:
const AWS = require('aws-sdk');
let cognito = new AWS.CognitoIdentityServiceProvider();
let request =
Username: userSub,
UserPoolId: process.env.userPoolId,
;
let result = await cognito.adminGetUser(request).promise();
const userAttributes = result.UserAttributes.reduce((acc, attribute) =>
const Name, Value = attribute;
acc[Name] = Value;
return acc;
, );
return userAttributes;
【讨论】:
用户名与 sub 不同。发现这个答案有效 - ***.com/a/47968938/5702727.【参考方案3】:这个问题——在 S3 路径中使用用户的sub
而不是他们的identityId
的问题以及如何设置它——在@JesseDavda 解决这个问题的帮助下为我解决了 100%本期:https://github.com/aws-amplify/amplify-js/issues/54
对于所有试图在 lambdas 中获取 identityId
以便您在 S3 中的 Amplify 默认路径工作的开发人员 - 此解决方案最终会完全忽略 identityId
- 这是一个设置路径的解决方案在 S3 中基于 sub
而不是 identityId
。在此解决方案结束时,您将永远不必为您的用户处理多个 id,您将永远不必再处理 identityId
(希望如此)。
【讨论】:
以上是关于带有 Cognito 的 AWS Lambda API 网关 - 如何使用 IdentityId 访问和更新 UserPool 属性?的主要内容,如果未能解决你的问题,请参考以下文章
AWS Lambda、API 网关和 Cognito:如何在 lambda 函数中获取身份对象?
如何在 API 网关上的 cognito 授权方保护的 lambda 函数中获取 AWS Cognito 用户数据
由于错误 AccessDeniedException(Lambda 别名作为 Cognito 触发器),AWS Cognito 用户池引发 PreSignUp 调用失败