本文介绍了使用AWS IOS SDK验证用户是否已通过身份验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了一个lamdba函数,它执行以下操作:

I created a lamdba function which does the following:

var param =
{
    IdentityPoolId: "us-east-1:the-full-identity-id",
    Logins: {} // To have provider name in a variable
};
param.Logins["com.test.website.login"] = userIdICreatedAndStoredInDynamoDB;

cognitoidentity.getOpenIdTokenForDeveloperIdentity(param,
function(err, data)
{
    if (err) return fn(err); // an error occurred
    else fn(null, data.IdentityId, data.Token); // successful response
});

它返回该用户的identityId和token。所有内容都设置了IAM角色和AWS Cognito Identity,并且似乎在控制台中进行身份验证。

It returns the identityId and token for that user. Everything is setup with IAM roles and AWS Cognito Identity and appears to be authenticating in the console.

我有两个问题:


  1. 如何在应用中测试用户是否经过身份验证?我在应用设备中保存了identityId和令牌。

  2. 验证持续多长时间?我希望用户保持登录状态。这就是我使用的大多数应用程序的工作方式并保持登录状态直到他们注销。

谢谢。

推荐答案

回答第一个问题:

你测试了通过制作自定义授权程序进行身份验证

You test the authentication by making a "Custom Authorizer"

当您创建新函数时,您可以在Lambda示例函数中找到AWS示例函数
(如果你过滤到NodeJS 4.3函数,那就是向后)

The AWS example function you can find in the Lambda Example Functions when you go to make a new function(if you filter to NodeJS 4.3 functions, it's towards the back)

或者你可以看看这是相同的东西,只是在GitHub代替。

Or you can take a look at THIS which is the same thing, just on GitHub instead.

我在这里修改了 sorta

"use strict";

const
    codes  = {
        100: "Continue", 101: "Switching Protocols", 102: "Processing",
        200: "OK", 201: "Created", 202: "Accepted", 203: "Non-Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi-Status", 208: "Already Reported", 226: "IM Used",
        300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect",
        400: "Bad Request", 401: "Unauthorized", 402: "Payment Required", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Payload Too Large", 414: "URI Too Long",
        415: "Unsupported Media Type", 416: "Range Not Satisfiable", 417: "Expectation Failed", 418: "I'm a teapot", 421: "Misdirected Request", 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 425: "Unordered Collection", 426: "Upgrade Required", 428: "Precondition Required", 429: "Too Many Requests", 431: "Request Header Fields Too Large", 451: "Unavailable For Legal Reasons",
        500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 506: "Variant Also Negotiates", 507: "Insufficient Storage", 508: "Loop Detected", 509: "Bandwidth Limit Exceeded", 510: "Not Extended", 511: "Network Authentication Required"
    },
    resp   = ( statusCode, data ) => ( { statusCode, message: codes[ statusCode ], data } ),
    AWS    = require( "aws-sdk" ),
    crypto = require( "crypto" ),
    COG    = new AWS.CognitoIdentity(),
    token  = {
        algorithm: "aes-256-ctr",
        encrypt: item => {
            item = JSON.stringify( item );
            let cipher = crypto.createCipher( token.algorithm, process.env.PoolId ),
                crypted = cipher.update( item, 'utf8', 'base64' );
            crypted += cipher.final( 'base64' );
            return crypted;
        },
        decrypt: item => {
            let decipher = crypto.createDecipher( token.algorithm, process.env.PoolId ),
                dec = decipher.update( item, 'base64', 'utf8' );
            dec += decipher.final( 'utf8' );
            return dec;
        }
    };

function AuthPolicy( principal, awsAccountId, apiOptions ) {
    this.awsAccountId = awsAccountId;
    this.principalId = principal;
    this.version = '2012-10-17';
    this.pathRegex = new RegExp( '^[/.a-zA-Z0-9-\*]+$' );
    this.allowMethods = [];
    this.denyMethods = [];

    if( !apiOptions || !apiOptions.restApiId ) this.restApiId = '*';
    else this.restApiId = apiOptions.restApiId;

    if( !apiOptions || !apiOptions.region ) this.region = '*';
    else this.region = apiOptions.region;

    if( !apiOptions || !apiOptions.stage ) this.stage = '*';
    else this.stage = apiOptions.stage;
}

AuthPolicy.HttpVerb = {
    GET: 'GET',
    POST: 'POST',
    PUT: 'PUT',
    PATCH: 'PATCH',
    HEAD: 'HEAD',
    DELETE: 'DELETE',
    OPTIONS: 'OPTIONS',
    ALL: '*',
};

AuthPolicy.prototype = ( function AuthPolicyClass() {

    function addMethod( effect, verb, resource, conditions ) {
        if( verb !== '*' && !Object.prototype.hasOwnProperty.call( AuthPolicy.HttpVerb, verb ) ) {
            throw new Error( `Invalid HTTP verb ${verb}. Allowed verbs in AuthPolicy.HttpVerb` );
        }

        if( !this.pathRegex.test( resource ) )
            throw new Error( `Invalid resource path: ${resource}. Path should match ${this.pathRegex}` );

        let cleanedResource = resource;

        if( resource.substring( 0, 1 ) === '/' )
            cleanedResource = resource.substring( 1, resource.length );

        const resourceArn = `arn:aws:execute-api:${this.region}:${this.awsAccountId}:${this.restApiId}/${this.stage}/${verb}/${cleanedResource}`;

        if( effect.toLowerCase() === 'allow' )
            this.allowMethods.push( {
                resourceArn,
                conditions,
            } );
        else if( effect.toLowerCase() === 'deny' )
            this.denyMethods.push( {
                resourceArn,
                conditions,
            } );
    }

    function getEmptyStatement( effect ) {
        const statement = {};
        statement.Action = 'execute-api:Invoke';
        statement.Effect = effect.substring( 0, 1 ).toUpperCase() + effect.substring( 1, effect.length ).toLowerCase();
        statement.Resource = [];

        return statement;
    }

    function getStatementsForEffect( effect, methods ) {
        const statements = [];

        if( methods.length > 0 ) {
            const statement = getEmptyStatement( effect );

            for( let i = 0; i < methods.length; i++ ) {
                const curMethod = methods[ i ];
                if( curMethod.conditions === null || curMethod.conditions.length === 0 )
                    statement.Resource.push( curMethod.resourceArn );
                else {
                    const conditionalStatement = getEmptyStatement( effect );
                    conditionalStatement.Resource.push( curMethod.resourceArn );
                    conditionalStatement.Condition = curMethod.conditions;
                    statements.push( conditionalStatement );
                }
            }

            if( statement.Resource !== null && statement.Resource.length > 0 )
                statements.push( statement );
        }
        return statements;
    }

    return {
        constructor: AuthPolicy,
        allowAllMethods() {
            addMethod.call( this, 'allow', '*', '*', null );
        },
        denyAllMethods() {
            addMethod.call( this, 'deny', '*', '*', null );
        },
        allowMethod( verb, resource ) {
            addMethod.call( this, 'allow', verb, resource, null );
        },
        denyMethod( verb, resource ) {
            addMethod.call( this, 'deny', verb, resource, null );
        },
        allowMethodWithConditions( verb, resource, conditions ) {
            addMethod.call( this, 'allow', verb, resource, conditions );
        },
        denyMethodWithConditions( verb, resource, conditions ) {
            addMethod.call( this, 'deny', verb, resource, conditions );
        },
        build() {
            if( ( !this.allowMethods || this.allowMethods.length === 0 ) &&
                ( !this.denyMethods || this.denyMethods.length === 0 ) )
                throw new Error( 'No statements defined for the policy' );

            const policy = {}, doc = {};
            policy.principalId = this.principalId;

            doc.Version = this.version;
            doc.Statement = [];
            doc.Statement = doc.Statement.concat( getStatementsForEffect.call( this, 'Allow', this.allowMethods ) );
            doc.Statement = doc.Statement.concat( getStatementsForEffect.call( this, 'Deny', this.denyMethods ) );

            policy.policyDocument = doc;

            return policy;
        },
    };
} () );


exports.handler = ( event, context, cb ) => {
    const
        principalId      = process.env.principalId,
        tmp              = event.methodArn.split( ':' ),
        apiGatewayArnTmp = tmp[ 5 ].split( '/' ),
        awsAccountId     = tmp[ 4 ],
        apiOptions       = {
            region: tmp[ 3 ],
            restApiId: apiGatewayArnTmp[ 0 ],
            stage: apiGatewayArnTmp[ 1 ]
        },
        policy = new AuthPolicy( principalId, awsAccountId, apiOptions );

    let response;

    if( !event.authorizationToken || typeof event.authorizationToken !== "string" )
        response = resp( 401 );

    let item = token.decrypt( event.authorizationToken );

    try { item = resp( 100, JSON.parse( item ) ); }
    catch( e ) { item = resp( 401 ); }

    if( item.statusCode !== 100 )
        response = resp( 401 );
    else if( item.data.Expiration <= new Date().getTime() )
        response = resp( 407 );
    else
        response = resp( 100 );

    if( response.statusCode >= 400 ) {
        policy.denyAllMethods();
        const authResponse = policy.build();
        authResponse.context = response;
        cb( null, authResponse );
    } else {
        COG.getCredentialsForIdentity( {
            IdentityId: item.data.IdentityId,
            Logins: {
                'cognito-identity.amazonaws.com': item.data.Token
            }
        }, ( e, d ) => {
            if( e ) {
                policy.denyAllMethods();
                response = resp( 401 );
            } else {
                policy.allowMethod( AuthPolicy.HttpVerb.GET, "/user" );
                policy.allowMethod( AuthPolicy.HttpVerb.DELETE, "/user" );
                response = resp( 202 );
            }

            const authResponse = policy.build();
            authResponse.context = response;
            cb( null, authResponse );
        } );
    }
};

上面是完整的例子......但是让我分解一下并解释为什么他们提供的那个没有那么有帮助。

Above is the full example... But let me break this down and explain why the one they provide is not as helpful.

以下是设置此步骤的步骤,以便您可以看到为什么它必须是这样的。

Here are the steps to setting this up so you can see why it has to be something like this.


  1. 转到Lambda并创建一个名为 Auth_isValid 的函数或类似的东西

  2. PoolId principalId 放入环境变量中,以便以后更改

  3. 转到API网关并进行链接

  4. 在左侧的API选项下,点击授权者

  5. 点击创建 - > 自定义授权商

  6. 填写您的Lambda Region,函数名称(应该自动填充),Authorizer名称,Identity Token Source(现在保持简单,只需 method.request.header.Authorization ,并且TTL可以是300.不要乱用执行角色或令牌验证表达式但是。

  7. 保存/更新它并返回Lambda - 稍后我们将与此授权程序挂钩。

  1. Go to Lambda and make a function called Auth_isValid or something like that
  2. Put your PoolId and principalId into the Environment Variables so it's easy to change later
  3. Head over to API Gateway and lets link this up
  4. Under API Options on the left side, hit Authorizers
  5. Click Create -> Custom Authorizer
  6. Fill in your Lambda Region, function name (should auto-fill), Authorizer name, Identity Token Source (keep it simple with method.request.header.Authorization for now, and TTL can be 300. Lets not mess with Execution role or token validation expression yet.
  7. Save/Update it and head back to Lambda - we'll hook up a function with this authorizer later.

好的,所以当你看看我的功能时,你会看到我在最顶端做了这个奇怪的加密/解密事情:

Ok so when you look at my function, you'll see that I do this weird encrypt/decrypt thing at the very top:

token  = {
    algorithm: "aes-256-ctr",
    encrypt: item => {
        item = JSON.stringify( item );
        let cipher = crypto.createCipher( token.algorithm, process.env.PoolId ),
            crypted = cipher.update( item, 'utf8', 'base64' );
        crypted += cipher.final( 'base64' );
        return crypted;
    },
    decrypt: item => {
        let decipher = crypto.createDecipher( token.algorithm, process.env.PoolId ),
            dec = decipher.update( item, 'base64', 'utf8' );
        dec += decipher.final( 'utf8' );
        return dec;
    }
};

基本上,我在加密密钥中包含一些我想要的项目,这样我就可以传递所有信息十分简单。
(我将标识池作为哈希传递给它,使其变得简单明了,只要你从未向前端发送身份池ID,我们就会很好!)

Basically, I wrap some items I want inside an encrypted key simple so I can pass all my information around easy-peasy.(I pass in the Identity Pool as a hash to make it cool and simple and as long as you never send the Identity Pool ID to the front end, we're good!)

自定义授权器需要一个令牌,而不是你所说的令牌或其他东西的JSON块(你可以做但看起来很愚蠢)

The Custom Authorizer requires one single token, not a JSON block of what you'll say is a "token" or something (which you could do but it looks dumb)

所以我们有一个统一的令牌传入,我调用解密函数来解包(我将在一个中显示加密示例)第二。

So we have one unified token that gets passed in and I call the decrypt function for this to unwrap (I'll show the encrypt example in a second.

现在有些人可能会说噢,这实际上不是加密,它很容易被弄清楚 - 我对此的回答是:你好,它本来就是无论如何,未加密的原始文本,为什么不轻松。

Now some people may say "oh well that's not actually encryption it could easily be figured out" - my answer to this is: "ya well it would have been unencrypted, raw text anyway, why not make it easy."

现在好了,你看到那个部分,一直到功能的底部。

Ok now that you see that part, head down to the bottom of the function.

let response;

if( !event.authorizationToken || typeof event.authorizationToken !== "string" )
    response = resp( 401 );

let item = token.decrypt( event.authorizationToken );

try { item = resp( 100, JSON.parse( item ) ); }
catch( e ) { item = resp( 401 ); }

if( item.statusCode !== 100 )
    response = resp( 401 );
else if( item.data.Expiration <= new Date().getTime() )
    response = resp( 407 );
else
    response = resp( 100 );

if( response.statusCode >= 400 ) {
    policy.denyAllMethods();
    const authResponse = policy.build();
    authResponse.context = response;
    cb( null, authResponse );
} else {
    COG.getCredentialsForIdentity( {
        IdentityId: item.data.IdentityId,
        Logins: {
            'cognito-identity.amazonaws.com': item.data.Token
        }
    }, ( e, d ) => {
        if( e ) {
            policy.denyAllMethods();
            response = resp( 401 );
        } else {
            policy.allowMethod( AuthPolicy.HttpVerb.GET, "/user" );
            policy.allowMethod( AuthPolicy.HttpVerb.DELETE, "/user" );
            response = resp( 202 );
        }

        const authResponse = policy.build();
        authResponse.context = response;
        cb( null, authResponse );
    } );
}

更新



{
    "type":"TOKEN",
    "authorizationToken":"<session_token>",
    "methodArn":"arn:aws:execute-api:<region>:<Account_ID>:<API_ID>/<Stage>/<Method>/<Resource_Path>"
}





{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "execute-api:Invoke",
            "Effect": "Deny",
            "Resource": [
                "arn:aws:execute-api:<region>:<Account_ID>:<API_ID>/<Stage>/*/*"
            ]
        }
    ]
}




所以在我的第一个如果检查,我确保 authorizationToken 在那里并且它是字符串,如果不是,我们说它是未经授权(每个人都应该知道并使用他们的状态代码)

So in my first if check, I make sure the authorizationToken is there and that it's a string, if it's not, we say it's Unauthorized (everyone should know and use their status codes)

其次,我解密了令牌,并确保使用 tr y-catch 尝试。如果进展不顺利,他们未经授权。如果确实如此,我们可以继续

Second, I decrypt the token and make sure that went well with a try-catch attempt. If it didn't go well, they're Unauthorized. if it did, we can Continue.

你会在令牌中看到我放了一个变量到期,这是我检查密钥是否曾被接受并且正确并且现在只是过期的方式。为此,我说需要代理身份验证。这告诉了我的前端,再次登录并给我新的信誉。不要忘记,此功能的目的只是检查我们是否获得授权。不做像刷新令牌这样的花哨的东西。

You'll see in the token, I put a variable Expiration, this is how I check if the key was once accepted and correct and is simply expired now. For this, I say Proxy Authentication Required. Which tells my front end, go call login again and give me new creds. Don't forget, the purpose of this function has to be only to check IF we're authorized. Not to do fancy things like refresh tokens.

接下来,我检查一切是否正常,并致电 denyAllMethods 并放入响应的上下文中的响应代码。 API网关非常挑剔,只需要简单地传递IAM格式的策略 - 没有其他信息或格式,或者如果没有指定,可能存在其他任何内容或

Next, I check if everything is good and call denyAllMethods and put the response code in the context of the response. API Gateway is very picky and only wants simply IAM formatted policies passed around - no other information or format or whatever may be in there if it's not specified HERE or HERE

如果一切正常,我会拨打 getCredentialsForIdentity - 使用 IdentityId 令牌,确保该令牌实际上也是有效的,并且然后我允许当时需要的功能。这些非常重要,只会将令牌验证为那些功能 - 换句话说。如果您在IAM中的IAM角色说它可以访问所有内容,这将说不,您只能在 / user GET >和 / user 上删除。所以不要让它欺骗你。毕竟这是自定义授权商

If everything is OK, I call getCredentialsForIdentity - using the IdentityId and Token, make sure that token is, in fact valid as well, and then I allow the functions needed at the time. These are very important and will validate the token to only those functions - in other words. If your IAM role in IAM says it can access everything, this will say no, you can only access GET on /user and DELETE on /user. So don't let it fool you. This is a custom authorizer after all.

接下来,我需要向您展示我如何将所有这些放入Login部分。我有相同的 token = {部分,但在我的登录功能中我添加了一个 getToken 函数:

Next, I need to show you how I put all this in from the Login part. I have the same token = { part but in my login function I added a getToken function:

token.getToken = obj => {
    return new Promise( ( res, rej ) => {
        COG.getOpenIdTokenForDeveloperIdentity( {
            IdentityPoolId: process.env.PoolId,
            Logins: {
                "com.whatever.developerIdthing": obj.email
            },
            TokenDuration: duration
        }, ( e, r ) => {
            r.Expiration = new Date().getTime() + ( duration * 1000 );
            if( e ) rej( e );
            else res( token.encrypt( r ) );
        } );
    } );
};

请注意:

部分。

这是你的第二个问题的答案:

This is the answer to your second question:

你创建一个 OpenIdToken 使用他们的电子邮件或任何你想要识别它们的东西, TokenDuration 。我建议这一周或两周,但如果你想要一年或者其他什么, 31536000 就是这样。另一种方法是创建一个只提供授权凭据的函数,而不是在 407 denyAll c $ c>场景出现,他们可以调用唯一的方法 allowMethod(POST,/ updateCreds); 或类似的东西。这样你就可以每隔一段时间刷新一次。

You create an OpenIdToken using their email or whatever you want to identify them and TokenDuration is in seconds. I would recommend making this a week or two but if you wanted a year long or something, 31536000 would be it. Another way of doing this is to make a function that only gives you authorized credentials, and instead of calling denyAll in the authorizer when a 407 scenario comes up, make the only method they can call allowMethod( POST, /updateCreds ); or something like that. This way you can refresh their stuff every once in a while.

伪的是:

删除:

if( response.statusCode >= 400 )
else

并执行:

if( statusCode >= 400 )
    denyAll
else if( statusCode === 407 )
    allow refresh function
else
    allow everything else

希望这有帮助!

这篇关于使用AWS IOS SDK验证用户是否已通过身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

05-16 08:23