validation用户使用AWS IOS SDK进行身份validation

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

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 }); 

它返回该用户的身份标识和标记。 所有内容都通过IAMangular色和AWS Cognito Identity进行设置,并在控制台中显示为正在进行身份validation。

我有两个问题:

  1. 如何在应用程序中testing用户的身份validation? 我将身份标识和令牌保存在应用程序设备中。
  2. authentication持续多久? 我希望用户保持login。这是我使用的大多数应用程序的工作方式,并保持login状态直到他们登出。

谢谢。

回答第一个问题:

如何在应用程序中testing用户的身份validation? 我将identityId和令牌保存在应用程序设备中。

您通过创build“自定义授权者”来testing身份validation

您可以在Lambda示例函数中find的AWS示例函数,当您开始创build一个新函数(如果您过滤到NodeJS 4.3函数,它是朝后)

或者你可以看看这是同样的事情,只是在GitHub上。

我在这里做了一个修改版:

 "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 ); } ); } }; 

上面是一个完整的例子…但让我打破这一点,并解释为什么他们提供的不是帮助。

这是设置这个步骤,所以你可以看到为什么它必须是这样的。

  1. 去Lambda并做一个称为Auth_isValid或类似的东西
  2. 将您的PoolIdprincipalId放入环境variables中,以便日后更改
  3. 转到API网关,让它连接起来
  4. 在左侧的API选项下,点击Authorizers
  5. 点击Create – > Custom Authorizer
  6. 填写您的Lambda地区,函数名称(应该自动填充),授权人名称,身份令牌来源(现在保持简单,使用method.request.header.Authorization ,TTL可以是300.让我们不要method.request.header.Authorization执行angular色或令牌validationexpression呢。
  7. 保存/更新它并返回到Lambda – 稍后我们将使用此授权者来连接一个函数。

好,所以当你看我的function,你会看到,我做这个奇怪的encryption/解密的东西在最上面:

 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; } }; 

基本上,我把一些我想要的内容包装在一个encryption的密钥中,所以我可以把所有的信息都传递给易于使用的人。 (我将身份池作为散列传递,使其变得很酷且简单,只要您不会将身份池ID发送到前端,我们就很棒!)

自定义授权程序需要一个单一的标记,而不是一个JSON块,你会说什么是一个“标记”或什么(你可以做,但它看起来愚蠢)

所以我们有一个传入的统一标记,我把这个decrypt函数叫做解包(我将在第二个示例中显示encryption示例。

现在有些人可能会说:“哦,这实际上并不是真正可以encryption的东西,我可以很容易的想到” – 我的回答是:“好吧,它本来是未encryption的,原始文本,为什么不简单呢。

好吧,现在你可以看到这个部分,下面这个函数的底部。

 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 ); } ); } 

更新

我们从API网关传入的数据是:

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

我们从Lambda传出的数据应该是这样的:

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

取决于我们的授权如何。

所以在我的第一个检查中,我确定authorizationToken在那里,而且是一个string ,如果不是的话,我们说它是Unauthorized (每个人都应该知道并使用他们的状态码)

其次,我解密令牌,并确保try-catch尝试。 如果不好,他们是Unauthorized 。 如果是的话,我们可以Continue

你会看到在令牌中,我把一个variablesExpiration ,这是我如何检查密钥是否被接受和正确,现在简单过期。 为此,我说Proxy Authentication Required 。 这告诉我的前端,再次呼叫login,给我新的信誉。 不要忘了,这个function的目的只是为了检查我们是否被授权。 不要做像刷新标记这样的花式东西。

接下来,我检查是否一切正常,并调用denyAllMethods并将响应代码放在响应的context中。 API网关是非常挑剔的,只希望简单的IAM格式化策略传递 – 没有其他信息或格式或任何可能在那里如果没有指定这里或这里

如果一切正常,我调用getCredentialsForIdentity (使用IdentityIdToken ,确保该令牌事实上也是有效的,然后允许当时所需的function。 这些是非常重要的, 并将validation令牌只有这些function – 换句话说。 如果您在IAM中的IAMangular色表示可以访问所有内容,则会显示为否,您只能访问/user上的GET/user DELETE 。 所以不要让它愚弄你。 毕竟这是一个自定义授权者

接下来,我需要告诉你我是如何把所有这一切从login部分。 我有相同的token = {部分,但在我的loginfunction,我添加了一个getToken函数:

 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 ) ); } ); } ); }; 

注意上面的:

duration

部分。

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

authentication持续多久? 我希望用户保持login。这是我使用的大多数应用程序的工作方式,并保持login状态直到他们登出。

您可以使用他们的电子邮件或任何您想要识别它们的方式创build一个OpenIdTokenTokenDuration几秒钟内 。 我会build议做这一两个星期,但如果你想要一年或什么的, 31536000就是了。 这样做的另一种方法是创build一个函数,只给你授权的凭据,而不是在授权者调用denyAll ,当407情况出现时,使他们唯一的方法可以调用allowMethod( POST, /updateCreds ); 或类似的东西。 这样你可以每隔一段时间刷新一下。

伪造的是:

去掉:

 if( response.statusCode >= 400 ) else 

并做:

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

希望这可以帮助!

要testing他们是否login,您需要设置一个服务来检查针对Cognito的令牌。 快速和肮脏的方法是设置一个基本的lambda,通过一个授权者指向你的用户身份池,通过API网关公开。 所有的lambda需要做的是返回HTTP 200,因为你真正检查的是授权者。 然后让你的应用得到/ post / etc到那个带有“授权”标头的API URL:$ ACCESS_TOKEN。 要么在成功时反击200,要么返回未经授权的消息。

您的Cognito令牌只有一个小时的使用时间,但您可以刷新令牌以保持一个人login。当您的用户进行身份validation时,他们有三个令牌:ID,Access和Refresh令牌。 您可以使用后者来请求新的访问令牌。

它被logging在: http : //docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html