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 и токен в приложении.

Вы проверить подлинности, сделав «Custom Authorizer»

Пример функции 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. Поместите свой PoolId и principalId в переменных окружения, так что легко изменить позже
  3. Зайдем к API шлюза и позволяет увязать это
  4. в разделе Параметры API на левой стороне, ударил Authorizers
  5. Нажмите Create ->Custom Authorizer
  6. Заполните свой лямбда-регион, имя функции (должно быть автозаполнено), имя авторизованного пользователя, источник токена идентичности (пока оно будет оставаться простым с method.request.header.Authorization, а TTL может быть 300. Позволяет не испортить роль Execution или выражение проверки токена.
  7. Сохраните/обновите его и вернитесь в Лямбду - мы подключим функцию с этим авторизатором позже.

ИТАК, когда вы посмотрите на мою функцию, вы увидите, что я делаю это странное шифровать/дешифровать вещь на самом верху:

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

В принципе, я обернуть некоторые элементы, которые я хочу внутри зашифрованный ключ, так что я могу передать всю свою информацию вокруг easy-peasy. (я передаю в Identity Pool в качестве хэша, чтобы сделать его крутым и простым, и до тех пор, пока вы никогда не отправляете идентификатор Pool Identity на передний план, мы хороши!)

Пользовательский авторизатор требует один единственный токен , а не блок 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); 
    }); 
} 

Update:

Наши данные, поступающие от 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 отформатированные политика розданы - никакой другой информации или формат или любой другой, может быть там, если это не указано HERE или HERE

Если все в порядке, я называю getCredentialsForIdentity - с помощью IdentityId и Token, убедитесь, что токен действительно действителен, а затем я разрешаю функции, необходимые в то время. Это очень важно, и будет проверять токен только на те функции - другими словами. Если ваша роль IAM в IAM говорит, что она может получить доступ ко всему, это скажет «нет», вы можете получить доступ только к GET по телефонам /user и DELETE по адресу /user. Так что не позволяйте этому обманывать вас. Это пользовательский авторизатор в конце концов.

Далее, я должен показать вам, как я все это ввел из части входа. У меня такая же 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

часть.

Это ответ на ваш второй вопрос:

Сколько времени аутентификации в последний раз? Я хочу, чтобы пользователь оставался вошедшим в систему. Это то, как большинство приложений, которые я использую, работают и остаются в системе, пока они не удалят выход из системы.

Вы создать OpenIdToken используя свою электронную почту или что вы хотите, чтобы идентифицировать их и TokenDuration находится в секунд. Я бы рекомендовал сделать это неделю или две, но если бы вы хотели год или что-то, 31536000 было бы это.Другой способ сделать это - сделать функцию, которая дает только авторизованные учетные данные, и вместо вызова 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 

Надеется, что это помогает!

0

Чтобы проверить, были ли они зарегистрированы, вам необходимо настроить службу, которая проверит токен против Cognito. Быстрый и грязный способ - создать базовую лямбду, разоблачить ее через API-шлюз с помощью авторизатора, указывающего на ваш пул идентификаторов пользователей. Все, что нужно сделать лямбда, это вернуть HTTP 200, поскольку то, что вы действительно проверяете, является авторизатором. Затем ваше приложение получит/опубликует/etc к URL-адресу API с заголовком «Авторизация»: $ ACCESS_TOKEN. либо он отбросит 200 на успех, либо вернет сообщение Unauthorized.

Ваш токен Cognito подходит только в течение часа, но вы можете обновить токен, чтобы пользователь вошел в систему. Когда ваш пользователь прошел аутентификацию, они получили три токена: идентификатор, доступ и токен обновления. Вы можете использовать последнее, чтобы запросить новый токен доступа.

Документально по адресу: http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html

+0

Я начал это до того, как были открыты пулы пользователей, поэтому моя вещь другая. Я получаю только идентификатор ID и токен. – cdub

+0

Вместо этого используйте федеративные идентификаторы. – cdub

Смежные вопросы