Amazon cloudformation AppSync:使用AWS_IAM auth时在$context中获取用户信息
在AppSync中,当您使用Cognito用户池作为身份验证设置时,您将获得Amazon cloudformation AppSync:使用AWS_IAM auth时在$context中获取用户信息,amazon-cloudformation,amazon-cognito,aws-appsync,aws-amplify,Amazon Cloudformation,Amazon Cognito,Aws Appsync,Aws Amplify,在AppSync中,当您使用Cognito用户池作为身份验证设置时,您将获得 identity: { sub: 'bcb5cd53-315a-40df-a41b-1db02a4c1bd9', issuer: 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_oicu812', username: 'skillet', claims: { sub: 'bcb5cd53-315a-40df
identity:
{ sub: 'bcb5cd53-315a-40df-a41b-1db02a4c1bd9',
issuer: 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_oicu812',
username: 'skillet',
claims:
{ sub: 'bcb5cd53-315a-40df-a41b-1db02a4c1bd9',
aud: '7re1oap5fhm3ngpje9r81vgpoe',
email_verified: true,
event_id: 'bb65ba5d-4689-11e8-bee7-2d0da8da81ab',
token_use: 'id',
auth_time: 1524441800,
iss: 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_oicu812',
'cognito:username': 'skillet',
exp: 1524459387,
iat: 1524455787,
email: 'myemail@nope.com' },
sourceIp: [ '11.222.33.200' ],
defaultAuthStrategy: 'ALLOW',
groups: null }
但是,当您使用AWS_IAM auth时,您会得到
identity:
{ accountId: '12121212121', //<--- my amazon account ID
cognitoIdentityPoolId: 'us-west-2:39b1f3e4-330e-40f6-b738-266682302b59',
cognitoIdentityId: 'us-west-2:a458498b-b1ac-46c1-9c5e-bf932bad0d95',
sourceIp: [ '33.222.11.200' ],
username: 'AROAJGBZT5A433EVW6O3Q:CognitoIdentityCredentials',
userArn: 'arn:aws:sts::454227793445:assumed-role/MEMORYCARDS-CognitoAuthorizedRole-dev/CognitoIdentityCredentials',
cognitoIdentityAuthType: 'authenticated',
cognitoIdentityAuthProvider: '"cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob","cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob:CognitoSignIn:1a072f08-5c61-4c89-807e-417d22702eb7"' }
身份:
{accountId:'1212121',//要通过AppSync API访问用户的用户名、电子邮件、sub等,有一个答案:
总而言之,您希望将用户池ID令牌发送到您的API(例如AppSync或API网关)。您的API请求经过IAM身份验证。然后在Lambda函数中验证ID令牌,现在您将验证的IAM用户池和用户池数据放在一起
您想使用IAM的identity.cognitoIdentityId
作为用户表的主键。添加ID令牌(用户名、电子邮件等)中包含的数据作为属性
通过这种方式,您可以通过API使用用户声明。例如,现在,您可以将$ctx.identity.cognitoIdentityId
设置为项目的所有者。然后,其他用户可以通过GraphQL解析器查看所有者的名称
如果您需要在您的解析器中访问用户的声明,恐怕目前不可能。我对此提出了一个问题,因为这对授权非常有帮助:
在这种情况下,您可以使用Lambda作为数据源,而不是使用解析器,并从上述用户表中检索用户的声明
目前一切都有点困难:)这里有一个不好的答案。我注意到,cognitoIdentityAuthProvider:““cognito idp.us-west-2.amazonaws.com/us-west-2_HighBob”,“cognito idp.us-west-2.amazonaws.com/us-west-2_HighBob:CognitoSignIn:1a072f08-5c61-4c89-807e-417d22702eb7”包含cognito用户的子项(CognitoSignIn之后的大图标)。您可以使用正则表达式提取它,并使用aws sdk从cognito用户池中获取用户信息
///////RETRIEVE THE AUTHENTICATED USER'S INFORMATION//////////
if(event.context.identity.cognitoIdentityAuthType === 'authenticated'){
let cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
//Extract the user's sub (ID) from one of the context indentity fields
//the REGEX in match looks for the strings btwn 'CognitoSignIn:' and '"', which represents the user sub
let userSub = event.context.identity.cognitoIdentityAuthProvider.match(/CognitoSignIn:(.*?)"/)[1];
let filter = 'sub = \"'+userSub+'\"' // string with format = 'sub = \"1a072f08-5c61-4c89-807e-417d22702eb7\"'
let usersData = await cognitoidentityserviceprovider.listUsers( {Filter: filter, UserPoolId: "us-west-2_KsyTKrQ2M",Limit: 1}).promise()
event.context.identity.user=usersData.Users[0];
}
这是一个错误的答案,因为您正在ping用户池数据库,而不仅仅是解码JWT。这是我的答案。appSync客户端库中存在一个错误,该错误将覆盖所有自定义头。这已被修复。现在,您可以将自定义头传递给解析程序,我将其传递给我的lambda函数(请再次注意,我使用的是lambda数据源,而不是dynamoDB)
因此,我将登录的JWT附加到客户端,并在服务器端的lambda函数中对其进行解码。您需要cognito创建的公钥来验证JWT。(您不需要密钥。)有一个“已知密钥”与每个用户池相关联的url,我在第一次启动lambda时对其进行ping,但就像我的mongoDB连接一样,它在lambda调用之间保持(至少保持一段时间)
这是lambda解析器
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const jwkToPem = require('jwk-to-pem');
const request = require('request-promise-native');
const _ = require('lodash')
//ITEMS THAT SHOULD BE PERSISTED BETWEEN LAMBDA EXECUTIONS
let conn = null; //MONGODB CONNECTION
let pem = null; //PROCESSED JWT PUBLIC KEY FOR OUR COGNITO USER POOL, SAME FOR EVERY USER
exports.graphqlHandler = async (event, lambdaContext) => {
// Make sure to add this so you can re-use `conn` between function calls.
// See https://www.mongodb.com/blog/post/serverless-development-with-nodejs-aws-lambda-mongodb-atlas
lambdaContext.callbackWaitsForEmptyEventLoop = false;
try{
////////////////// AUTHORIZATION/USER INFO /////////////////////////
//ADD USER INFO, IF A LOGGED IN USER WITH VALID JWT MAKES THE REQUEST
var token = _.get(event,'context.request.headers.jwt'); //equivalen to "token = event.context.re; quest.headers.alexauthorization;" but fails gracefully
if(token){
//GET THE ID OF THE PUBLIC KEY (KID) FROM THE TOKEN HEADER
var decodedToken = jwt.decode(token, {complete: true});
// GET THE PUBLIC KEY TO NEEDED TO VERIFY THE SIGNATURE (no private/secret key needed)
if(!pem){
await request({ //blocking, waits for public key if you don't already have it
uri:`https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}/.well-known/jwks.json`,
resolveWithFullResponse: true //Otherwise only the responce body would be returned
})
.then(function ( resp) {
if(resp.statusCode != 200){
throw new Error(resp.statusCode,`Request of JWT key with unexpected statusCode: expecting 200, received ${resp.statusCode}`);
}
let {body} = resp; //GET THE REPSONCE BODY
body = JSON.parse(body); //body is a string, convert it to JSON
// body is an array of more than one JW keys. User the key id in the JWT header to select the correct key object
var keyObject = _.find(body.keys,{"kid":decodedToken.header.kid});
pem = jwkToPem(keyObject);//convert jwk to pem
});
}
//VERIFY THE JWT SIGNATURE. IF THE SIGNATURE IS VALID, THEN ADD THE JWT TO THE IDENTITY OBJECT.
jwt.verify(token, pem, function(error, decoded) {//not async
if(error){
console.error(error);
throw new Error(401,error);
}
event.context.identity.user=decoded;
});
}
return run(event)
} catch (error) {//catch all errors and return them in an orderly manner
console.error(error);
throw new Error(error);
}
};
//async/await keywords used for asynchronous calls to prevent lambda function from returning before mongodb interactions return
async function run(event) {
// `conn` is in the global scope, Lambda may retain it between function calls thanks to `callbackWaitsForEmptyEventLoop`.
if (conn == null) {
//connect asyncoronously to mongodb
conn = await mongoose.createConnection(process.env.MONGO_URL);
//define the mongoose Schema
let mySchema = new mongoose.Schema({
///my mongoose schem
});
mySchema('toJSON', { virtuals: true }); //will include both id and _id
conn.model('mySchema', mySchema );
}
//Get the mongoose Model from the Schema
let mod = conn.model('mySchema');
switch(event.field) {
case "getOne": {
return mod.findById(event.context.arguments.id);
} break;
case "getAll": {
return mod.find()
} break;
default: {
throw new Error ("Lambda handler error: Unknown field, unable to resolve " + event.field);
} break;
}
}
这比我的另一个“坏”答案要好得多,因为你并不总是查询数据库来获取你在客户端已经拥有的信息。根据我的经验,大约快3倍。如果你使用AWS Amplify,我所做的就是设置自定义标题用户名
,如前所述:
Amplify.configure({
API: {
graphql_headers: async () => ({
// 'My-Custom-Header': 'my value'
username: 'myUsername'
})
}
});
然后在我的解析器中,我可以通过以下方式访问标题:
$context.request.headers.username
正如AppSync在基于Honkskillets answer的访问请求标题一节中的文档所解释的,我已经编写了一个lambda函数,它将返回用户属性。您只需为该函数提供JWT即可
const jwt=require(“jsonwebtoken”);
const jwkToPem=require(“jwk到pem”);
常量请求=要求(“请求承诺”);
exports.handler=异步(事件、上下文)=>{
试一试{
const{token}=事件;
const decodedToken=jwt.decode(标记,{complete:true});
const publicJWT=等待请求(
`https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER\u POOL\u ID}/.well-known/jwks.json`
);
const keyObject=JSON.parse(publicJWT.keys.find)(
key=>key.kid==decodedToken.header.kid
);
const pem=jwkToPem(键对象);
返回{
状态代码:200,
正文:jwt.verify(令牌,pem)
};
}捕获(错误){
控制台错误(error);
返回{
状态代码:500,
正文:error.message
};
}
};
我在Appsync中使用它,创建管道解析程序,并在需要用户属性时添加此函数。我通过使用$context从解析程序中的头抓取JWT来提供JWT。request
因此,当您发送UserPoolID令牌时,您是否将其作为头发送?我不知道如何在使用时附加自定义头g AWS使用AppSync进行放大。@honkskillet这里的要点是有一个API方法(例如,称为syncUser)仅用于将用户池ID令牌保存到数据库中。因此,我将ID令牌作为此API方法中的唯一参数。例如,当用户登录时,您可以进行此API调用。好的,我明白了。如果您使用DynamoDB,这可能是当前唯一可行的解决方法。我使用的是lambda数据源,因此这并不重要我可以访问VTL模板中的用户信息。我只需要在Lambda函数中使用它。我的“错误答案”这种方法的缺点是,当数据应该在原始请求中正确时,对数据库进行不必要的调用。速度慢。我认为每个用户都可以伪造他们想要的任何用户名。用户名可能不是很糟糕,但如果使用sub进行访问控制,则会变得非常危险。这正是糟糕的答案我在找。