AWS API 基于用户组的网关自定义授权方
AWS API Gateway Custom Authorizer based on User Groups
我正在尝试设计一个系统,其中在我的 AWS 用户池中创建用户并将其分配给四个用户组之一。这些用户组具有附加的角色,这些角色指定了允许他们进行的 API 调用。我已经为每个组创建了一个用户,并且能够在我的 Android 应用程序中成功登录到他们。我的用户池还附加到一个身份池,用于处理身份联合的单点登录。
问题是,当我登录用户时,分配给用户的角色似乎不是来自身份池而不是他们的用户组,而是假设分配给用户组的角色,并且作为结果他们无法进行他们应该有权访问的 api 调用。
我试图通过在 Node.js 中实施自定义授权器来解决此问题,但脚本似乎 运行 出现了一些问题。每当它进入 ValidateToken() 方法时,它都会说令牌不是 JWT 令牌。
console.log('Loading function');
var jwt = require('jsonwebtoken');
var request = require('request');
var jwkToPem = require('jwk-to-pem');
var groupName = 'MY_GROUP_NAME';
var roleName = 'MY_ROLE_NAME';
var policyName = 'MY_POLICY_NAME';
var userPoolId = 'MY_USER_POOL_ID';
var region = 'MY_REGION';
var iss = 'https://cognito-idp.' + region + '.amazonaws.com/' + userPoolId;
var pems;
exports.handler = function(event, context) {
//Download PEM for your UserPool if not already downloaded
if (!pems) {
//Download the JWKs and save it as PEM
request({
url: iss + '/.well-known/jwks.json',
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
pems = {};
var keys = body['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;
}
//Now continue with validating the token
ValidateToken(pems, event, context);
} else {
//Unable to download JWKs, fail the call
context.fail("error");
}
});
} else {
//PEMs are already downloaded, continue with validating the token
ValidateToken(pems, event, context);
};
};
function ValidateToken(pems, event, context) {
var token = event.authorizationToken;
//Fail if the token is not jwt
var decodedJwt = jwt.decode(token, {complete: true});
if (!decodedJwt) {
//THIS IS WHERE THE SCRIPT ENDS UP
console.log("Not a valid JWT token");
context.fail("Unauthorized - Invalid Token Provided");
return;
}
//Fail if token is not from your UserPool
if (decodedJwt.payload.iss != iss) {
console.log("invalid issuer");
context.fail("Unauthorized - Invalid Issuer Provided");
return;
}
//Reject the jwt if it's not an 'Access Token'
if (decodedJwt.payload.token_use != 'access') {
console.log("Not an access token");
context.fail("Unauthorized - Not an Access Token");
return;
}
//Get the kid from the token and retrieve corresponding PEM
var kid = decodedJwt.header.kid;
var pem = pems[kid];
if (!pem) {
console.log('Invalid access token');
context.fail("Unauthorized - Invalid Access Token Provided");
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) {
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not verify token signature");
}
else {
//Valid token. Generate the API Gateway policy for the user
//Always generate the policy on value of 'sub' claim and not for 'username' because username is reassignable
//sub is UUID for a user which is never reassigned to another user.
var principalId = payload.sub;
var username = payload.username;
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
var params = {
UserPoolId: userPoolId, /* ID of the Target User Pool */
Username: username, /* Provided by event object??? */
Limit: 0,
NextToken: '' //May need actual token value
};
cognitoidentityserviceprovider.adminListGroupsForUser(params, function(err, data) {
if (err){
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not obtain Groups for User");
}
else{
var groups = data.Groups;
var numGroups = groups.length;
var isFound = false;
for(var i = 0; i < numGroups; i++){
if(groups[i].GroupName == groupName){
isFound = true;
}
}
if(isFound){
var iam = new AWS.IAM();
var iamParams = {
PolicyName: policyName, /* Name of the Policy in the User Group Role */
RoleName: roleName /* Name of the User Group Role */
};
iam.getRolePolicy(params, function(err, data) {
if (err){
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not acquire Policy for User Group Role");
}
else {
var policy = data.PolicyDocument;
context.succeed(policy); //May need to build policy
}
});
}
else{
context.fail("Unauthorized - Could not find the required User Group under the User");
}
}
});
}
});
}
任何人都可以确定此脚本的问题,或者可以帮助我确定为什么设置的令牌不是有效的 JWT 令牌吗?令牌由 Android 应用程序使用 AWS Cognito SDK 发送。
编辑:经过进一步调查,从 event.authorizationToken 检索到的令牌具有以下格式([VALUE] 块用于隐藏潜在的敏感信息):
AWS4-HMAC-SHA256 Credential=[VALUE1]/20170329/us-east-1/execute-api/aws4_request,
SignedHeaders=host;x-amz-date;x-amz-security-token,
Signature=[VALUE2]
如果客户端在登录后获取 AWS 凭据,您只能在 API 网关方法上使用 AWS_IAM 授权类型。您看到的 authorizationToken 值是客户端使用 Cognito 提供的凭据生成的 AWS 签名。您将无法在自定义授权方中验证 AWS 签名。
你在关注这个Cognito blog post吗?如果是这样,我认为您可能会将用户组角色与身份池上经过身份验证的角色选择混淆了。当您将联合身份与用户池提供程序一起使用时,您的客户端将从身份池的 Cognito 选项卡的该部分取回具有 'Authenticated role' 权限的 AWS 凭证。在博客 post 中,这将是身份池上的 'EngineerRole' 集。
我想通了:
这个document(特别是底部)说"If you set roles for groups in an Amazon Cognito user pool, those roles are passed through the user's ID token. To use these roles, you must also set Choose role from token for the authenticated role selection for the identity pool."
只需为每个角色设置适当的信任策略,调整身份池以使用 "Choose role from token" 与用户池身份验证提供程序,现在就可以承担适当的角色。对于其他 运行 遇到此问题的人,这是我的信任政策:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "[IDENTITY_POOL_ID]"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
我正在尝试设计一个系统,其中在我的 AWS 用户池中创建用户并将其分配给四个用户组之一。这些用户组具有附加的角色,这些角色指定了允许他们进行的 API 调用。我已经为每个组创建了一个用户,并且能够在我的 Android 应用程序中成功登录到他们。我的用户池还附加到一个身份池,用于处理身份联合的单点登录。
问题是,当我登录用户时,分配给用户的角色似乎不是来自身份池而不是他们的用户组,而是假设分配给用户组的角色,并且作为结果他们无法进行他们应该有权访问的 api 调用。
我试图通过在 Node.js 中实施自定义授权器来解决此问题,但脚本似乎 运行 出现了一些问题。每当它进入 ValidateToken() 方法时,它都会说令牌不是 JWT 令牌。
console.log('Loading function');
var jwt = require('jsonwebtoken');
var request = require('request');
var jwkToPem = require('jwk-to-pem');
var groupName = 'MY_GROUP_NAME';
var roleName = 'MY_ROLE_NAME';
var policyName = 'MY_POLICY_NAME';
var userPoolId = 'MY_USER_POOL_ID';
var region = 'MY_REGION';
var iss = 'https://cognito-idp.' + region + '.amazonaws.com/' + userPoolId;
var pems;
exports.handler = function(event, context) {
//Download PEM for your UserPool if not already downloaded
if (!pems) {
//Download the JWKs and save it as PEM
request({
url: iss + '/.well-known/jwks.json',
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
pems = {};
var keys = body['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;
}
//Now continue with validating the token
ValidateToken(pems, event, context);
} else {
//Unable to download JWKs, fail the call
context.fail("error");
}
});
} else {
//PEMs are already downloaded, continue with validating the token
ValidateToken(pems, event, context);
};
};
function ValidateToken(pems, event, context) {
var token = event.authorizationToken;
//Fail if the token is not jwt
var decodedJwt = jwt.decode(token, {complete: true});
if (!decodedJwt) {
//THIS IS WHERE THE SCRIPT ENDS UP
console.log("Not a valid JWT token");
context.fail("Unauthorized - Invalid Token Provided");
return;
}
//Fail if token is not from your UserPool
if (decodedJwt.payload.iss != iss) {
console.log("invalid issuer");
context.fail("Unauthorized - Invalid Issuer Provided");
return;
}
//Reject the jwt if it's not an 'Access Token'
if (decodedJwt.payload.token_use != 'access') {
console.log("Not an access token");
context.fail("Unauthorized - Not an Access Token");
return;
}
//Get the kid from the token and retrieve corresponding PEM
var kid = decodedJwt.header.kid;
var pem = pems[kid];
if (!pem) {
console.log('Invalid access token');
context.fail("Unauthorized - Invalid Access Token Provided");
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) {
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not verify token signature");
}
else {
//Valid token. Generate the API Gateway policy for the user
//Always generate the policy on value of 'sub' claim and not for 'username' because username is reassignable
//sub is UUID for a user which is never reassigned to another user.
var principalId = payload.sub;
var username = payload.username;
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
var params = {
UserPoolId: userPoolId, /* ID of the Target User Pool */
Username: username, /* Provided by event object??? */
Limit: 0,
NextToken: '' //May need actual token value
};
cognitoidentityserviceprovider.adminListGroupsForUser(params, function(err, data) {
if (err){
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not obtain Groups for User");
}
else{
var groups = data.Groups;
var numGroups = groups.length;
var isFound = false;
for(var i = 0; i < numGroups; i++){
if(groups[i].GroupName == groupName){
isFound = true;
}
}
if(isFound){
var iam = new AWS.IAM();
var iamParams = {
PolicyName: policyName, /* Name of the Policy in the User Group Role */
RoleName: roleName /* Name of the User Group Role */
};
iam.getRolePolicy(params, function(err, data) {
if (err){
console.log(err, err.stack); // an error occurred
context.fail("Unauthorized - Could not acquire Policy for User Group Role");
}
else {
var policy = data.PolicyDocument;
context.succeed(policy); //May need to build policy
}
});
}
else{
context.fail("Unauthorized - Could not find the required User Group under the User");
}
}
});
}
});
}
任何人都可以确定此脚本的问题,或者可以帮助我确定为什么设置的令牌不是有效的 JWT 令牌吗?令牌由 Android 应用程序使用 AWS Cognito SDK 发送。
编辑:经过进一步调查,从 event.authorizationToken 检索到的令牌具有以下格式([VALUE] 块用于隐藏潜在的敏感信息):
AWS4-HMAC-SHA256 Credential=[VALUE1]/20170329/us-east-1/execute-api/aws4_request,
SignedHeaders=host;x-amz-date;x-amz-security-token,
Signature=[VALUE2]
如果客户端在登录后获取 AWS 凭据,您只能在 API 网关方法上使用 AWS_IAM 授权类型。您看到的 authorizationToken 值是客户端使用 Cognito 提供的凭据生成的 AWS 签名。您将无法在自定义授权方中验证 AWS 签名。
你在关注这个Cognito blog post吗?如果是这样,我认为您可能会将用户组角色与身份池上经过身份验证的角色选择混淆了。当您将联合身份与用户池提供程序一起使用时,您的客户端将从身份池的 Cognito 选项卡的该部分取回具有 'Authenticated role' 权限的 AWS 凭证。在博客 post 中,这将是身份池上的 'EngineerRole' 集。
我想通了: 这个document(特别是底部)说"If you set roles for groups in an Amazon Cognito user pool, those roles are passed through the user's ID token. To use these roles, you must also set Choose role from token for the authenticated role selection for the identity pool."
只需为每个角色设置适当的信任策略,调整身份池以使用 "Choose role from token" 与用户池身份验证提供程序,现在就可以承担适当的角色。对于其他 运行 遇到此问题的人,这是我的信任政策:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "[IDENTITY_POOL_ID]"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}