3

驗證我創建了一個lamdba函數執行以下操作:驗證用戶使用AWS IOS SDK

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和令牌的用戶。所有內容均通過IAM角色和AWS Cognito Identity進行設置,並且似乎在控制檯中進行了身份驗證。

我有兩個問題:

  1. 如何在應用測試用戶進行身份驗證?我將身份標識和令牌保存在應用設備中。
  2. 認證持續多久?我希望用戶保持登錄狀態。這是我使用的大多數應用程序的工作方式,並保持登錄狀態,直到他們登出。

謝謝。

+0

仍在尋找一個好的答案。 – cdub

+0

請參閱下面的評論以獲取更多信息。 – cdub

回答

4

要回答第一個問題:

如何在用戶進行身份驗證的應用程序測試

它是在記錄?我將identityId和令牌保存在應用設備中。

您通過進行「自定義授權者」測試認證

的AWS實例功能,您可以在lambda功能。例如發現當你去做出新的功能 (如果您篩選的NodeJS 4.3功能,它的背面)

或者你可以看看THIS這是同樣的事情,只是在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到環境變量,所以很容易轉移到API網關更改後
  3. 頭,讓鏈接這件事
  4. 在左側API選項,打Authorizers
  5. 點擊Create - >Custom Authorizer
  6. 填寫您的Lambda區域,函數名稱(應自動填充),授權人名稱,身份令牌源(現在簡單地使用method.request.header.Authorization,並且TTL可以是300)。讓我們不要混淆執行角色或令牌驗證表達式。
  7. 保存/更新它並返回到Lambda - 稍後我們將使用此授權人與功能掛鉤。

好了,所以當你在看我的功能,你會看到,我做這種怪異的加密/在解密事情的頂部:

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

基本上,我換一些項目我想裏面的加密密鑰很簡單,所以我可以將所有信息傳遞給易於使用的人。 (我將Identity Pool作爲散列傳遞給它,使其變得既酷又簡單,只要您從不將身份池ID發送到前端,我們就很好!)

Custom Authorizer需要一個令牌,而不是一個JSON塊,你會說什麼是一個「標記」或什麼(你可以做,但它看起來愚蠢)

所以我們有一個統一的令牌傳入,我呼籲decrypt函數爲此(我會在一秒鐘內顯示加密示例)

現在有些人可能會說「哦,這實際上不是加密,它可以很容易地找出來」 - 我對此的回答是:「你好吧它會無論如何都是未加密的,原始文本,爲什麼不容易呢。「

好吧,現在你已經看到了這個部分,頭部下降到函數的底部。

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>/*/*" 
      ] 
     } 
    ] 
} 

根據我們的授權情況而定。


所以在我的第一if檢查,我確保authorizationToken是存在的,它是一個string,如果不是的話,我們說這是Unauthorized(大家應該知道並使用他們的狀態碼)

其次,我解密令牌並確保嘗試執行try-catch。如果它不好,他們是Unauthorized。如果是這樣,我們可以Continue

你會看到在令牌中,我把一個變量Expiration,這是我如何檢查密鑰是否被接受和正確,現在只是過期。爲此,我說Proxy Authentication Required。它告訴我的前端,再次呼叫登錄並給我新的信譽。不要忘記,這個功能的目的只是爲了檢查我們是否被授權。不要做像刷新標記這樣的花式東西。

接下來,我檢查是否一切正常,並致電denyAllMethods並將響應代碼放在響應的context中。 API網關是非常挑剔,只想要簡單的IAM格式化政策傳來傳去 - 沒有其它信息或格式或任何可能在那裏,如果不是指定它HEREHERE

如果一切正常,我叫getCredentialsForIdentity - 使用IdentityIdToken,確保令牌實際上也是有效的,然後我允許當時需要的功能。這些是非常重要的,並且將驗證令牌僅僅是那些功能 - 換句話說。如果您在IAM中的IAM角色表示可以訪問所有內容,則將顯示爲否,您只能在/user和上訪問GETDELETE。所以不要讓它愚弄你。畢竟,這是一個定製授權人

接下來,我需要向您展示如何將所有這些內容從Login部分放入。我有同樣的token = {的一部分,但在我的登錄功能,我添加了一個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

部分。

這是回答你的第二個問題:

多久認證持續多久?我希望用戶保持登錄狀態。這是我使用的大多數應用程序的工作方式,並保持登錄狀態,直到他們登出。

創建使用任何你想找出他們和TokenDuration他們的電子郵件或OpenIdToken。我會建議做這一兩個星期,但如果你想要一年或什麼東西,31536000就是了。這樣做的另一種方法是創建一個僅授予您授權憑據的功能,並且在407情景出現時,不要在授權人中調用denyAll,而是使其唯一方法可以調用allowMethod(POST, /updateCreds);或類似的東西。這樣,你可以每隔一段時間刷新一次。

爲僞是:

刪除:

if(response.statusCode >= 400) 
else 

,做:

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

希望這有助於!

0

要測試它們是否已登錄,您需要設置一個服務,該服務將檢查針對Cognito的令牌。快速和骯髒的方法是設置一個基本的lambda,通過授權者指向您的用戶標識池,通過API網關公開它。所有的lambda需要做的是返回HTTP 200,因爲你真正檢查的是授權者。然後讓你的應用獲得/發佈/ etc到帶有「授權」標頭的API URL:$ ACCESS_TOKEN。要麼它會在成功時反擊200,要麼會返回未經授權的消息。

Cognit令牌只適用於一個小時,但您可以刷新該令牌以保持一個人登錄。當您的用戶通過身份驗證時,他們有三個令牌:ID,Access和Refresh令牌。您可以使用後者來請求新的訪問令牌。 http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html

+0

我在用戶池可用之前就開始了這個,所以我的事情是不同的。我只獲得一個身份標識和令牌。 – cdub

+0

改爲使用聯合身份。 – cdub