基于用户组的AWS API网关自定义授权程序

时间:2017-03-29 18:38:39

标签: android amazon-web-services jwt aws-api-gateway aws-cognito

我正在尝试设计一个系统,用户在我的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]

2 个答案:

答案 0 :(得分:2)

如果客户端在登录后获取AWS凭据,则只能在API网关方法上使用AWS_IAM授权类型。您看到的authorizationToken值是客户端使用Cognito提供的凭据生成的AWS签名。您无法在自定义授权程序中验证AWS签名。

您是否关注此Cognito blog post?如果是这样,我认为您可能会将用户组角色与身份池上经过身份验证的角色选择混淆。将联合身份与用户池提供程序一起使用时,您的客户端将从身份池的Cognito选项卡中获取具有“已验证角色”权限的AWS凭据。在博客文章中,这将是在身份池上设置的“EngineerRole”。

答案 1 :(得分:1)

我明白了: 此document(特别是底部部分)表示&#34;如果您为Amazon Cognito用户池中的组设置角色,则这些角色将通过用户的ID令牌传递。要使用这些角色,还必须为身份池的经过身份验证的角色选择设置从令牌中选择角色。&#34;

所需要的只是为每个角色设置适当的信任策略,调整要使用的身份池&#34;从令牌中选择角色&#34;使用用户池身份验证提供程序,现在假定正确的角色。对于遇到此问题的其他人,这是我的信任政策:

{
  "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"
        }
      }
    }
  ]
}