Полный веб-шаблон AWS - Урок 1B

Нажмите здесь для основного содержания

Часть A: Начальная настройка

Часть B: Основные функции

Часть C: Последние шаги к полноценности

Скачайте Github здесь.

SDK Javascript Cognito

Большой! Вы должны быть здесь, только если вы завершили первоначальную настройку Cognito и Federated Identities. Теперь, когда у нас все настроено, пришло время пройтись по коду Javascript. Загрузите Шаблон Kangzeroo на Github и обязательно войдите в ветку Cognito, где мы будем выполнять свою работу.

$ git clone https://github.com/kangzeroo/Kangzeroos-Complete-AWS-Web-Boilerplate.git
$ cd Kangzeroos-Complete-AWS-Web-Boilerplate
$ git checkout Cognito
$ cd App

В шаблоне используется библиотека Amazon-Cognito-Identity-JS, которую можно найти на github. Эта библиотека упрощает использование программного обеспечения AWS Cognito, но те же функции можно найти в исходном aws-sdk. Итак, давайте установим наши зависимости и загрузим приложение.

$ npm install
$ npm run start

Настройка профиля AWS

Перейдите к App/src/components/Auth, где мы найдем все компоненты React, связанные с аутентификацией Cognito. Да, в этом руководстве используется React, но вы можете легко применить те же уроки к другим фреймворкам JS. Перейдите к App/src/api/aws/aws-cognito.js, где находится основная часть кода AWS Cognito. Давайте посмотрим на зависимости и то, как мы можем настроить наш собственный профиль AWS.

// aws-cognito.js
import { CognitoUserPool, CognitoUserAttribute, CognitoUser, AuthenticationDetails, CognitoIdentityCredentials, WebIdentityCredentials } from 'amazon-cognito-identity-js';
import { userPool, LANDLORD_USERPOOL_ID, LANDLORD_IDENTITY_POOL_ID, TENANT_IDENTITY_POOL_ID } from './aws_profile'
import uuid from 'node-uuid'

Мы импортируем различные функции как из amazon-cognito-identity-js, так и из ./aws_profile.js. Функции из amazon-cognito-identity-js будут объяснены по мере продвижения. Мы хотим сосредоточиться на ./aws_profile.js материалах. Здесь мы помещаем наши параметры Cognito, такие как userPoolId и AppIds. Давайте посмотрим на ./aws_profile.js.

import { CognitoUserPool } from 'amazon-cognito-identity-js';
import 'amazon-cognito-js'
const REGION = "us-east-1"
const USER_POOL_ID = 'us-east-1_6i5p2Fwao'
const CLIENT_ID = '5jr0qvudipsikhk2n1ltcq684b'
AWS.config.update({
 region: REGION
})
const userData = {
    UserPoolId : USER_POOL_ID,
    ClientId : CLIENT_ID
}
export const userPool = new CognitoUserPool(userData);
export const USERPOOL_ID = 'cognito-idp.'+REGION+'.amazonaws.com/'+USER_POOL_ID
export const IDENTITY_POOL_ID = 'us-east-1:65bd1e7d-546c-4f8c-b1bc-9e3e571cfaa7'

Здесь мы настраиваем AWS region, Cognito USER_POOL_ID и приложение Cognito CLIENT_ID. Мы также создаем объект CognitoUserPool из наших USER_POOL_ID и CLIENT_ID, который содержит большую часть наших функций Cognito. CognitoUserPool имеет функции для всего, от сброса пароля до аутентификации нового пользователя. Здесь мы создаем CognitoUserPool, чтобы нам не пришлось повторно создавать его экземпляр для каждой функции. Мы также настроили наш USERPOOL_ID, который является URL-адресом, который требуется в некоторых функциях Cognito и состоит из USER_POOL_ID и region. Наконец, мы также экспортируем ARN нашего пула федеративных удостоверений, который также требуется в некоторых функциях Cognito. В общем, это просто настройка, и вам нужно только скопировать и вставить свои собственные значения.

В последнем наборе настроек мы создадим массив наших пользовательских атрибутов в верхней части нашего aws_cognito.js файла. Заполните массив собственными пользовательскими атрибутами и не забудьте добавить к любым настраиваемым атрибутам префикс custom: вместо const attrs. const landlordAttrs не требует префикса custom:.

// we create an array of all attributes, without the `custom:` prefix. 
// This will be used for building the React-Redux object in plain JS, hence no AWS Cognito related name requirements
const landlordAttrs = ["email", "agentName", "id"]
// we create an array of all our desired attributes for changing, and we loop through this array to access the key name.
// This will be used for AWS Cognito related name requirements
const attrs = ["custom:agentName"]

Теперь, после настройки и перед тем, как начать собственно код, я должен сказать, что этот шаблон готов. Вам не обязательно знать, что происходит за кулисами. Вы можете просто использовать этот шаблон, и все функции будут работать из коробки: регистрация, вход в систему, проверка электронной почты, сброс пароля, изменение атрибутов пользователя и сохраненные логины с использованием JWT. Обратите внимание, что любые электронные письма, которые вы используете в Cognito, должны быть проверены AWS, пока вы не попросите покинуть песочницу AWS SES (в этом случае вы сможете отправить любое электронное сообщение). Если вы здесь только ради рабочего шаблона, это все, что вам нужно прочитать. Если вы хотите знать, что происходит, продолжайте читать!

Регистрация пользователей

Давайте посмотрим на первый компонент аутентификации под названием SignUp.js. Я не буду тратить время на объяснение стороны React, поскольку это не является целью данного руководства. Найдите эту функцию:

signup(){
...
// call the AWS Cognito function that we named `signUpUser`
    signUpUser(this.state)
     .then(({email})=>{
      // if successful, then save the email to localStorage so we can pre-fill the email form on the login & verify account screens
      localStorage.setItem('User_Email', email)
      // re-route to the verify account screen
      browserHistory.push('/auth/verify_account')
     })
...
}

Здесь мы вызываем signUpUser() и передаем состояние компонента React, которое выглядит следующим образом:

this.state = {
   email: "",
   agentName: "",
   password: "",
   confirmPassword: "",
   errorMessage: null,
   loading: false
}

Мы будем использовать атрибуты электронной почты и пароля состояния только тогда, когда мы передадим их в signUpUser(). ФункцияsignUpUser() находится в App/src/api/aws/aws-cognito.js.

export function signUpUser({email, agentName, password}){
 const p = new Promise((res, rej)=>{
  const attributeList = []
  const dataEmail = {
      Name : 'email',
      Value : email
  }
  const dataAgentName = {
      Name : 'custom:agentName',
      Value : agentName
  }
  const attributeEmail = new CognitoUserAttribute(dataEmail)
  const attributeAgentName = new CognitoUserAttribute(dataAgentName)
  attributeList.push(attributeEmail, attributeAgentName)
  userPool.signUp(email, password, attributeList, null, function(err, result){
      if (err) {
          rej(err)
          return
      }
      res({email})
  })
 })
 return p
}

signUpUser() принимает объект, который должен иметь 3 атрибута: email, password и agentName. Чтобы сохранить его как атрибут нашего пользователя Cognito, мы должны создать CognitoUserAttribute объект для каждого. Мы делаем это, создавая объект dataEmail и dataAgentName с именем атрибута и его значением. Эти объекты будут переданы в CognitoUserAttribute функцию, которая преобразует их в читаемые объекты AWS Cognito, которые мы назвали attributeEmail и attributeAgentName. Обратите внимание, что dataAgentName.name имеет префикс custom:, чтобы указать Cognito, что agentName является настраиваемым атрибутом пользователя. Теперь, когда у нас есть объекты CognitoUserAttribute, мы поместим их в массив attributeList.

Следующая строка кода - это то, что на самом деле регистрирует этого нового пользователя. Мы используем объект userPool, который мы импортировали из ./aws_profile.js, и вызываем его функцию signUp. Первые 3 аргумента - это уникальный идентификатор email, password и массив attributeList. Четвертый аргумент равен нулю, а пятый - обратный вызов. В обратном вызове мы отклоняем обещание, если произошла ошибка, а если ошибок нет, мы выполняем обещание. В шаблоне мы возвращаем электронное письмо для сохранения в localStorage в нашем компоненте React, но это не обязательно. Вы можете разрешить обещание ни с чем. Создан новый пользователь Cognito. Но для того, чтобы использовать нового пользователя, он должен иметь возможность подтвердить свою учетную запись. Инфраструктура AWS для этого уже настроена, поэтому теперь все, что нам нужно сделать, это пройтись по коду функции проверки.

подтвердить учетную запись

Мы не будем тратить слишком много времени на эту часть кода, поэтому сначала я объясню компонент React-Redux на высоком уровне, а затем подробно расскажу о материалах AWS.

На высоком уровне после регистрации пользователя react-router перенаправляет его на URL-адрес /verify_account, где появляется компонент App/src/components/Auth/VerifyAccount.js. Когда компонент смонтирован, поле электронной почты автоматически заполняется при доступе к localStorage. Затем у нас есть возможность ввести проверочный PIN-код, отправленный на электронную почту пользователя, или выбрать сброс и повторную отправку проверочного PIN-кода. Давайте посмотрим на функцию resetVerificationPIN() в App/src/api/aws/aws-cognito.js.

export function resetVerificationPIN(email){
 const p = new Promise((res, rej)=>{
  const userData = {
   Username: email,
   Pool: userPool
  }
  const cognitoUser = new CognitoUser(userData)
  cognitoUser.resendConfirmationCode(function(err, result) {
         if (err) {
          rej(err)
          return
         }
         res()
     })
 })
 return p
}

Когда мы вызываем эту функцию, нам нужно только передать электронное письмо. Cognito автоматически проверит, существует ли электронное письмо, и выдаст ошибку, если это не так. Как и в SignUp.js, мы создаем объект userData, содержащий адрес электронной почты нашего пользователя и userPool, импортированный из ./aws_profile.js, чтобы создать объект vliad CognitoUser. Используя объект CognitoUser, мы можем вызвать resendConfirmationCode(), чтобы PIN-код был отправлен снова. Вот и все!

Теперь давайте посмотрим на функцию verifyUserAccount():

export function verifyUserAccount({email, pin}){
 const p = new Promise((res, rej)=>{
  const userData = {
   Username: email,
   Pool: userPool
  }
  const cognitoUser = new CognitoUser(userData)
  cognitoUser.confirmRegistration(pin, true, function(err, result) {
         if (err) {
             console.log(err);
             rej(err)
             return;
         }
         if(result == "SUCCESS"){
          console.log("Successfully verified account!")
          cognitoUser.signOut()
          res()
         }else{
          rej("Could not verify account")
         }
     })
 })
 return p
}

verifyUserAccount() принимает объект как единственный аргумент, содержащий 2 основных атрибута email и pin. Мы создаем еще один объект userData, чтобы создать CognitoUser, чтобы вызвать функцию confirmRegistration(). В confirmRegistration() мы передаем pin, true и обратный вызов. Если подтверждение прошло успешно, мы выходим из системы с когнитивным пользователем (чтобы мы могли снова войти в систему и обновить пользователя). Если это не удается, мы отклоняем обещание. Довольно просто, поскольку SDK абстрагирует многие детали. После успешной проверки компонент React перенаправит вас на страницу входа.

Войти Пользователи

Давайте посмотрим на следующий компонент App/src/components/Auth/Login.js. Найдите следующую функцию:

signin(){
  this.setState({loading: true})
  signInUser({
   email: this.state.email,
   password: this.state.password
  }).then((userProfileObject)=>{
   localStorage.setItem('User_Email', this.state.email)
   this.props.setUser(userProfileObject)
   browserHistory.push('/authenticated_page')
  })
  .catch((err)=>{
   this.setState({
    errorMessage: err.message,
    loading: false
   })
  })
 }

Что здесь происходит? Сначала мы вызываем функцию signInUser() Cognito, чтобы войти в систему и получить данные пользователя из Cognito. Затем в цепочке обещаний мы сохраняем адрес электронной почты пользователя в localStorage, чтобы мы могли автоматически установить адрес электронной почты при следующем входе в систему. Мы также сохраняем пользователя в состоянии Redux, используя this.props.setUser(), который является функцией действия Redux, расположенной в App/src/actions/auth_actions.js. Мы не будем рассматривать React-Redux, поскольку это не является основной темой данного руководства. Давайте посмотрим на функцию AWS Cognito.

Найдите signInUser() в App/src/api/aws/aws-cognito.js. Вот как это выглядит:

export function signInUser({email, password}){
 const p = new Promise((res, rej)=>{
  const authenticationDetails = new AuthenticationDetails({
   Username: email,
   Password: password
  })
  const userData = {
   Username: email,
   Pool: userPool
  }
  const cognitoUser = new CognitoUser(userData)
  authenticateUser(cognitoUser, authenticationDetails)
   .then(()=>{
    return buildUserObject(cognitoUser)
   })
   .then((userProfileObject)=>{
    res(userProfileObject)
   })
   .catch((err)=>{
    rej(err)
   })
 })
 return p
}

Мы создаем объект AuthenticationDetails Cognito, содержащий пользователя email и password. Мы также создаем объект CognitoUser для его authenticateUser() функции, но обратите внимание, что authenticateUser() не объявлен нигде в функции или в верхней части страницы, где мы перечисляем зависимости. Это потому, что authenticateUser() - это еще одна функция, объявленная ниже по странице. На странице объявлена ​​еще одна функция buildUserObject(), которая берет атрибуты пользователя из Cognito и форматирует их в пользовательский объект, который мы хотим использовать в состоянии Redux. В конце цепочки обещаний мы возвращаем userProfileObject, который выводит buildUserObject(). Давайте пройдемся по цепочке обещаний, начиная с authenticateUser().

function authenticateUser(cognitoUser, authenticationDetails){
 const p = new Promise((res, rej)=>{
  cognitoUser.authenticateUser(authenticationDetails, {
         onSuccess: function (result) {
             localStorage.setItem('user_token', result.accessToken.jwtToken)
             const loginsObj = {
                 [USERPOOL_ID]: result.getIdToken().getJwtToken()
             }
       AWS.config.credentials = new AWS.CognitoIdentityCredentials({
                 IdentityPoolId : IDENTITY_POOL_ID, 
                 Logins : loginsObj
             })
             AWS.config.credentials.refresh(function(){
              console.log(AWS.config.credentials)
             })
             res()
         },
         onFailure: function(err) {
             rej(err)
         }
     })
 })
 return p
}

authenticateUser() принимает аргументы cognitoUser и authenticationDetails и использует их для двух целей. cognitoUser содержит функцию authenticateUser(), которую мы будем вызывать для входа в AWS Cognito. Первый аргумент, который мы передаем, - это authenticationDetails (который содержит адрес электронной почты + пароль), а второй аргумент - это объект с обратным вызовом onSuccess и onFailure. Просто onFailure просто отклонит цепочку обещаний. onSuccess будет содержать result, в котором будет использоваться токен JWT для будущей аутентификации без необходимости ввода пароля. Мы сохраняем JWT в localStorage и извлекаем его всякий раз, когда он нам нужен (внутренняя аутентификация для ресурсов или автоматический вход в систему). Затем мы создаем loginsObj, который содержит ключ-значение нашего USER_POOL_ID и токена JWT. Мы передаем этот loginsObj экземпляру AWS.config.credentials, используя new AWS.CognitoIdentityCredentials() вместе с IdentityPoolId. Для этого нужно зарегистрировать логин в AWS Federated Identities. Напомним, что Federated Identities используется для управления входами из нескольких источников, поэтому имеет смысл использовать Federated Identities для записи успешного входа в систему для каждого входа в систему.

После настройки AWS.config.credentials мы можем теперь использовать аутентификацию Cognito для запроса других сервисов Amazon. Конечно, эти службы необходимо настроить для внесения в белый список определенной аутентификации Cognito (и отклонения других запросов), но это будет показано в будущих руководствах для каждой службы. В любом случае, после настройки AWS.config.credentials важно обновить учетные данные с помощью AWS.config.credentials.refresh, чтобы AWS использовала последнюю, которую мы только что добавили.

Теперь перейдем к следующему шагу в цепочке signInUser() обещаний: buildUserObject().

function buildUserObject(cognitoUser){
 const p = new Promise((res, rej)=>{
  cognitoUser.getUserAttributes(function(err, result) {
         if (err) {
              rej(err)
              return
         }
         let userProfileObject = {}
         for (let i = 0; i < result.length; i++) {
           if(result[i].getName().indexOf('custom:') >= 0){
              let name = result[i].getName().slice(7, result[i].getName().length)
              userProfileObject[name] = result[i].getValue()
           }else{
              userProfileObject[result[i].getName()] = result[i].getValue()
           }
         }
         res(userProfileObject)
     })
 })
 return p
}

Сначала передается объект cognitoUser и используется для вызова его метода getUserAttributes(). Как всегда, мы отклоняем обещание, если возникает ошибка. В случае успеха мы приступаем к созданию emptyuserProfileObject, который будет иметь структуру, соответствующую тому, что мы хотим, в нашем интерфейсе React-Redux. Объект result, который мы получаем в результате успешного обратного вызова, представляет собой массив объектов CognitoUserAttribute (вспомните массивAttributeList из signUpUser()). Мы перебираем этот массив с помощью цикла for и получаем имена каждого атрибута, при необходимости удаляя префикс custom:. Затем мы также включаем значение атрибута и добавляем пару «ключ-значение» в userProfileObject. К концу цикла у нас будет готовый userProfileObject на простом JS. Мы возвращаем userProfileObject и завершаем цепочку signInUser() обещаний. Давайте снова посмотрим на поток signInUser() и понаблюдаем за потоком на высоком уровне.

export function signInUser({email, password}){
 const p = new Promise((res, rej)=>{
const authenticationDetails = new AuthenticationDetails({
   Username: email,
   Password: password
  })
const userData = {
   Username: email,
   Pool: userPool
  }
  const cognitoUser = new CognitoUser(userData)
authenticateUser(cognitoUser, authenticationDetails)
   .then(()=>{
    return buildUserObject(cognitoUser)
   })
   .then((userProfileObject)=>{
    res(userProfileObject)
   })
   .catch((err)=>{
    rej(err)
   })
})
 return p
}

Когда мы, наконец, разрешаем signInUser() обещание, мы возвращаем userProfileObject компоненту React. В компоненте React Login.js посмотрите, что мы делаем после signInUser().

signin(){
  this.setState({loading: true})
  signInUser({
   email: this.state.email,
   password: this.state.password
  }).then((userProfileObject)=>{
   localStorage.setItem('User_Email', this.state.email)
   this.props.setUser(userProfileObject)
   browserHistory.push('/authenticated_page')  
  })
  .catch((err)=>{
   this.setState({
    errorMessage: err.message,
    loading: false
   })
  })
 }

Мы сохраняем адрес электронной почты пользователя в localStorage для использования в будущем и добавляем userProfileObject в состояние Redux. Если какие-либо ошибки произошли во всем процессе, они будут обнаружены и отображены в this.state.errorMessage. Вот и все! Небольшое примечание: this.props.setUser() - это действие Redux, которое устанавливает доступ к userProfileObject во всем приложении Redux, мы также переключаем логическое значение state.auth.authenticated на true. Приложение Redux использует state.auth.authenticated как средство определения, должна ли отображаться определенная страница. Например, мы хотим отображать страницу профиля пользователя только в том случае, если пользователь вошел в систему.

Заключение, часть 2

Вау, это была длинная статья! Но мы еще не закончили. Нам нужно затронуть еще несколько тем, включая updateUserInfo(), forgotPassword(), retrieveUserFromLocalStorage(), signOutUser() и внутреннюю аутентификацию токенов JWT для ограниченных ресурсов. Я сказал, что это ПОЛНОЕ руководство по AWS, не так ли? В любом случае, продолжайте читать, если чувствуете, что вам нужно знать, что происходит под капотом. Просто помните, что в любой момент вы можете прекратить чтение и просто использовать шаблон как есть, и он будет работать. Я надеюсь, что вы нашли эту серию полезной до сих пор. Увидимся в Cognito, часть 3!

Нажмите здесь для основного содержания

Часть A: Начальная настройка

Часть B: Основные функции

Часть C: Последние шаги к полноценности

Эти методы частично использовались при развертывании renthero.ca.