问题描述
我创建了一个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.
我有两个问题:
- 如何在应用中测试用户是否经过身份验证?我在应用设备中保存了identityId和令牌。
- 验证持续多长时间?我希望用户保持登录状态。这就是我使用的大多数应用程序的工作方式并保持登录状态直到他们注销。
谢谢。
推荐答案
回答第一个问题:
你测试了通过制作自定义授权程序进行身份验证
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.
- 转到Lambda并创建一个名为
Auth_isValid
的函数或类似的东西 - 将
PoolId
和principalId
放入环境变量中,以便以后更改 - 转到API网关并进行链接
- 在左侧的API选项下,点击
授权者
- 点击
创建
- >自定义授权商
- 填写您的Lambda Region,函数名称(应该自动填充),Authorizer名称,Identity Token Source(现在保持简单,只需
method.request.header.Authorization
,并且TTL可以是300.不要乱用执行角色或令牌验证表达式但是。 - 保存/更新它并返回Lambda - 稍后我们将与此授权程序挂钩。
- Go to Lambda and make a function called
Auth_isValid
or something like that - Put your
PoolId
andprincipalId
into the Environment Variables so it's easy to change later - Head over to API Gateway and lets link this up
- Under API Options on the left side, hit
Authorizers
- Click
Create
->Custom Authorizer
- 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. - 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验证用户是否已通过身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!