aws cognito_使用AWS Cognito进行用户管理—(3/3)全面完成的最后步骤

单于承
2023-12-01

aws cognito

by Kangze Huang

黄康泽

使用AWS Cognito进行用户管理—(3/3)全面完成的最后步骤 (User Management with AWS Cognito — (3/3) Last Steps to Full-Fledged)

完整的AWS Web样板-第1C部分 (The Complete AWS Web Boilerplate — Part 1C)

Main Table of Contents Click Here

主要目录请点击这里

Part A: Initial Setup

A部分: 初始设置

Part B: The Core Functionality

B部分: 核心功能

Part C: Last Steps to Full Fledged

C部分: 全面完成的最后步骤

Download the Github here.

此处下载Github。

最后一步 (The Last Steps)

The last pieces to this grand schema comprise of finishing touches and backend authentication. What we mean by finishing touches include:

此宏架构的最后一部分包括完成工作和后端身份验证。 我们所说的修饰包括:

- updateUserInfo( )
-updateUserInfo()
- forgotPassword( )
- 忘记密码( )
- signOutUser( )
-signOutUser()
- retreiveUserFromLocalStorage( )
-retreiveUserFromLocalStorage()
- Backend Authentication
-后端验证

Backend authentication means checking the JWT token received from Cognito or Facebook to confirm authority to access protected resources. After covering these features, we will have a full fledged user management system completely on AWS. Wow! Let’s get to it.

后端身份验证意味着检查从Cognito或Facebook收到的JWT令牌,以确认访问受保护资源的权限。 涵盖了这些功能之后,我们将在AWS上完全拥有完整的成熟用户管理系统。 哇! 让我们开始吧。

更新用户信息 (Updating User Information)

Any respectable user management system will have the ability to change user attributes, so AWS Cognito is no exception. The React component can be found at App/src/components/auth/ProfilePage.js. Our Cognito code is inside App/src/api/aws/aws-cognito.js, look for the function updateUserInfo().

任何受人尊敬的用户管理系统都可以更改用户属性,因此AWS Cognito也不例外。 可以在App/src/components/auth/ProfilePage.js找到React组件。 我们的Cognito代码位于App/src/api/aws/aws-cognito.js ,查找函数updateUserInfo()

export function updateUserInfo(editedInfo){ const p = new Promise((res, rej)=>{  const attributeList = []  for(let a = 0; a<attrs.length; a++){    if(editedInfo[attrs[a]]){      let attribute = {          Name : attrs[a],          Value : editedInfo[attrs[a]]      }      let x = new CognitoUserAttribute(attribute)      attributeList.push(x)    }  }  const cognitoUser = userPool.getCurrentUser()  cognitoUser.getSession(function(err, result) {      if(result){        cognitoUser.updateAttributes(attributeList, function(err, result) {          if(err){            rej(err)            return          }          cognitoUser.getUserAttributes(function(err, result) {            if(err){              rej(err)              return            }            buildUserObject(cognitoUser)             .then((userProfileObject)=>{              res(userProfileObject)             })          })        });      }    }); }) return p}

We pass in the user object that we got from buildUserObject(), but edited to include updated values (in this case, agentName). Inside our promise we create an empty attributeList array to hold the variables, which feels a lot like signUpUser(). That’s because it shares thesame process! We loop through the editedInfo object and for each attribute we create an CognitoUserAttribute object to add to the attributeList array.

我们传入从buildUserObject()获得的用户对象,但是将其编辑为包括更新后的值(在本例中为agentName )。 在我们的诺言中,我们创建一个空的attributeList数组来保存变量,感觉很像signUpUser() 。 那是因为它具有相同的过程! 我们遍历editedInfo对象,并为每个属性创建一个CognitoUserAttribute对象,以添加到attributeList数组中。

After all this is done, we create a CognitoUser object from the imported userPool to refresh the session so that we can appropriately call updateAttributes with the attributeList array. That will update our Cognito user with the latest attributes, and in the callback we can getUserAttributes again. With the updated attributes, we call buildUserObject for use in our React-Redux app. And that’s it! Almost an exact repeat of signUpUser().

完成所有这些操作后,我们从导入的userPool创建一个CognitoUser对象以刷新会话,以便我们可以使用attributeList数组适当地调用updateAttributes 。 这将使用最新属性更新Cognito用户,并在回调中再次getUserAttributes 。 使用更新的属性,我们调用buildUserObject以在React-Redux应用程序中使用。 就是这样! 几乎完全重复signUpUser()

忘记密码 (Forgot Password)

This one is also dead simple. As always, create your CognitoUser with data from userData. Now we can call forgotPassword.

这也很简单。 与往常一样,使用userData数据创建CognitoUser 。 现在我们可以调用forgotPassword

export function forgotPassword(email){ const p = new Promise((res, rej)=>{
const userData = {     Username: email,     Pool: userPool   }  const cognitoUser = new CognitoUser(userData)
cognitoUser.forgotPassword({      onSuccess: function (result) {        res({          cognitoUser: cognitoUser,          thirdArg: this        })      },      onFailure: function(err) {         rej(err)      },      inputVerificationCode: function(data) {         res({            cognitoUser: cognitoUser,            thirdArg: this         })      }  })
}) return p}

forgotPassword() basically initializes the process and returns a CognitoUser object to be used in React-Redux. In the callback object, we only use onSuccess and onFailure. inputVerificationCode is not used here like how it is used in the Github docs (see case 12) since we want to make a prettier interface instead of using prompt() to ask for input. In onSucccess, we return CognitoUser to our React component because we want the password reset page to contain pre-filled info (eg. email). When the new password is submitted, confirmPassword() just needs to accept a PIN, password and the this declaration from the AWS api call. This is what it looks like in the React-Redux app, from App/src/components/Auth/ResetPassword.js.

forgotPassword()基本上会初始化进程,并返回要在React-Redux中使用的CognitoUser对象。 在回调对象中,我们仅使用onSuccessonFailure 。 这里不像在Github文档中那样使用inputVerificationCode (请参阅案例12),因为我们想要创建一个更漂亮的界面,而不是使用prompt()来请求输入。 在onSucccess ,我们将CognitoUser返回到我们的React组件,因为我们希望密码重置页面包含预先填写的信息(例如email )。 提交新密码后, confirmPassword()仅需接受PINpassword和AWS api调用中的this声明。 这是从App/src/components/Auth/ResetPassword.js在React-Redux应用程序中看起来的样子。

verifyPin(){  if(this.props.password == this.props.confirm_password){     this.state.cognitoUserPackage.cognitoUser       .confirmPassword(this.state.pin, this.state.password, this.state.cognitoUserPackage.thirdArg)     setTimeout(()=>{       browserHistory.push("/auth/login")     }, 500)  } }

And that’s all there is to resetting a password — Not too complicated, not much code.

这就是重置密码的全部内容-不太复杂,代码也不多。

登出用户 (Sign Out User)

Signing out users is really simple. We call getCurrentUser() to instantiate our CognitoUser object so that we can use its signOut() function. Now your user is logged out!

注销用户非常简单。 我们调用getCurrentUser()实例化CognitoUser对象,以便我们可以使用其signOut()函数。 现在,您的用户已注销!

export function signOutUser(){ const p = new Promise((res, rej)=>{  const cognitoUser = userPool.getCurrentUser()  cognitoUser.signOut() }) return p}

How do we execute this from the UI? In our React-Redux boilerplate, app routing is handled by react-router while app state is handled by Redux. We must combine the two to integrate visual user authentication. Our goals here are:

我们如何从UI执行此操作? 在我们的React-Redux样板中,应用程序路由由react-router处理,而应用程序状态由Redux处理。 我们必须将两者结合起来以集成可视用户身份验证。 我们的目标是:

— Show different screens for authenticated or unauthenticated visitors
—为经过身份验证或未经身份验证的访问者显示不同的屏幕
— Restrict app access to unauthorized visitors
—限制未经授权的访客访问应用程序

Ok so here we go. First, let’s observe our Redux state expressed inApp/src/reducers/AuthReducer.js. Our state model looks like this:

好了,我们开始吧。 首先,让我们观察一下App/src/reducers/AuthReducer.js表示的Redux状态。 我们的状态模型如下所示:

const INITIAL_STATE = {  authenticated: false,  user: null}

When we logged in, we set the Redux state authenticated to true, and user to the return value of buildUserObject() in App/src/api/aws/aws-cognito.js . Thus INITIAL_STATE.authenticated will be used as a check universally throughout our app to determine if the user is authenticated. In our app boilerplate, we access this Redux state variable as this.props.authenticated. So at our side-menu component located at App/src/components/SideMenu/SideMenu.js, find this clip of code:

登录时,我们将Redux状态的authenticated设置为true,并将user设置为App/src/api/aws/aws-cognito.jsbuildUserObject()的返回值。 因此, INITIAL_STATE.authenticated将在我们的应用程序中普遍用作检查,以确定用户是否已通过身份验证。 在我们的应用样本中,我们以this.props.authenticated访问此Redux状态变量。 因此,在App/src/components/SideMenu/SideMenu.js的侧面菜单组件中,找到以下代码片段:

<div id='mainview' style={comStyles(this.props.sideMenuVisible).mainview}>        <SideHeader />        <SideOption text='Home' link='/' />        {           this.props.authenticated           ?           <SideOption text='Sign Out' link='/auth/signout' />           :           <SideOption text='Login' link='/auth/login' />        }</div>

After <SideOption text=’Home’ link=’/’ />, inside the curly brackets { } , we have a ternary operation (aka conditional operator). This will check if the first argument this.props.authenticated is truthy, and if true will display <SideOption text=’Sign Out’ link=’/auth/signout’ />. If false, will display <SideOption text=’Login’ link=’/auth/login’ />. And there we have it! Different views for authenticated & unauthenticated visitors.

<SideOption text='Home' link='/' />之后,在花cke {}中,我们有一个三元运算(也称为条件运算符)。 这将检查ument this.props.authent的第一个参数是否真实,如果为true,则将splay <SideOption text='Sign Out' link='/auth/si signout'/>。 如果为假,将ill display <SideOption text='Login' link=' / auth / login'/>。 我们终于得到它了! 身份验证和未经身份验证的访问者的不同视图。

Next, go to the code expressing our react-router located at App/src/index.js and find this code snip:

接下来,转到位于App/src/index.js表示我们的react-router的代码,并找到以下代码片段:

<Route path='/' component={App}>        <IndexRoute component={Home} />        <Route path='auth'>          <Route path='login' component={Login}></Route>          <Route path='signup' component={SignUp}></Route>          <Route path='signout' component={SignOut}></Route>          <Route path='verify_account' component={VerifyAccount}></Route>          <Route path='forgot_password' component={ResetPassword}></Route>          <Route path='authenticated_page' component={RequireAuth(AuthenticatedPage)}></Route>        </Route></Route>

A quick run-down of our app’s url tree. At http://ourApp.com/ we go to the Home component represented as App/src/components/home.js. At http://ourApp.com/auth/login is the Login component represented as App/src/components/Auth/Login.js. But at http://ourApp.com/auth/authenticated_page we have this slightly different route

快速查看我们应用的网址树。 在http://ourApp.com/我们转到Home组件,表示为App/src/components/home.jshttp://ourApp.com/auth/login处,登录组件表示为App/src/components/Auth/Login.js 。 但是在http://ourApp.com/auth/authenticated_page我们有一条略有不同的路线

<Route path=’authenticated_page’ component={RequireAuth(AuthenticatedPage)}></Route>

This route has RequireAuth() wrapping the AuthenticatedPage component. If we go to RequireAuth() at App/src/components/auth/RequireAuth.js, we find another component, but with no generated HTML (that is, no UI). This is a higher-order component (HOC) which only adds functionality. In this case, the HOC checks if the Redux state variable this.props.authenticated is truthy. If it is not truthy, we simply redirect the url to a different url path (in this case, http://ourApp.com/auth/login). Done, that’s the auth checker!

此路由具有包装AuthenticatedPage组件的RequireAuth() 。 如果在App / src / components / auth / RequireAuth.js上转到RequireAuth(),则会找到另一个组件,但是没有生成HTML(即,没有UI)。 这是仅添加功能的高阶组件(HOC)。 在这种情况下,HOC会检查Redux状态变量this.props.authenticated是否真实。 如果不正确,我们只需将URL重定向到其他URL路径(在本例中为http://ourApp.com/auth/login )。 完成,这就是授权检查器!

componentWillMount(){   if(!this.props.authenticated){    browserHistory.push('/auth/login')   }}

Now go back to App/src/index.js to implement the auth checking. We simply wrap the visual component inside our non-visual HCO like a function:

现在回到App/src/index.js来实现身份验证检查。 我们只需将视觉组件像函数一样包装在非视觉HCO中:

<Route path='authenticated_page' component={RequireAuth(AuthenticatedPage)}></Route>

And that’s the 2nd objective complete. The final part is our general purpose signout HCO. Go to App/src/components/auth/SignOut.js and find this snip of code:

这就是第二个目标。 最后一部分是我们的通用退出HCO。 转到App/src/components/auth/SignOut.js并找到以下代码App/src/components/auth/SignOut.js

componentWillMount(){    signOutUser()  // signoutLandlord() is a function from `actions` coming from index.js  this.props.logoutUserFromReduxState()  setTimeout(()=>{   browserHistory.push('/auth/login')  }, 500) }

The signOutUser() function is the one we wrote in App/src/api/aws/aws-cognit.js. Next this.props.logoutUserFromReduxState() sets our Redux state variable authenticated to false. Finally we change the url address and app view using browserHistory.push(‘/auth/login’) after half a second (So that we can display a goodbye message).

signOutUser()函数是我们在App/src/api/aws/aws-cognit.js 。 接下来, this.props.logoutUserFromReduxState()将经过authenticated的Redux状态变量设置为false。 最后,半秒后,我们使用browserHistory.push('/auth/login')更改了网址地址和应用视图(这样我们就可以显示再见消息了)。

And that’s it! You can now control every visual view of your app with authentication in mind! Let’s continue on to the next part.

就是这样! 现在,您可以记住身份验证来控制应用程序的每个可视视图! 让我们继续下一部分。

从本地存储中检索用户 (Retrieve User From Local Storage)

We don’t want our users to have to re-login each time they visit the web app. Ideally, we want their login to be saved and auto-logged in each visit until they log out. Since it is insecure to save a user’s password, we will instead store the JWT token. This is actually managed for us by AWS Cognito. Go to App/src/components/Auth/Login.js and find the following lines of code:

我们不希望用户每次访问Web应用程序时都必须重新登录。 理想情况下,我们希望保存他们的登录信息,并在每次访问前自动登录,直到他们注销。 由于保存用户密码是不安全的,因此我们将存储JWT令牌。 实际上,这是由AWS Cognito为我们管理的。 转到App/src/components/Auth/Login.js并找到以下代码行:

componentDidMount(){  const savedEmail = localStorage.getItem('User_Email')  if(savedEmail){   this.setState({    email: savedEmail   })  }  retrieveUserFromLocalStorage()   .then((data)=>{    this.props.setUserToReduxState(data)   }) }

The componentDidMount() function will be ran once after the component mounts onto the web page, which is when we want to check if a user has a saved login already. We can ignore the first part, which just checks for a saved email and sets it to the React component’s state. The important part here is retrieveUserFromLocalStorage(), which returns a userProfileObject that we can save to the Redux state. Recall that the userProfileObject is used by the web app as a representation of who the user is, and all their attributes such as name, age, height..etc. Straightforward, so let’s look at the juicy stuff: the AWS function. Go to App/src/api/aws/aws-cognito.js and find the function retrieveUserFromLocalStorage().

组件安装到网页后, componentDidMount()函数将运行一次,这是我们要检查用户是否已经保存了登录名的时候。 我们可以忽略第一部分,该部分仅检查已保存的电子邮件并将其设置为React组件的状态。 这里的重要部分是retrieveUserFromLocalStorage() ,它返回一个userProfileObject ,我们可以将其保存为Redux状态。 回想一下,web应用程序将userProfileObject用作用户身份及其所有属性(例如名称,年龄,身高等)的表示。 简单来说,让我们看一下多汁的东西:AWS函数。 转到App/src/api/aws/aws-cognito.js并找到函数App/src/api/aws/aws-cognito.js retrieveUserFromLocalStorage()

export function retrieveUserFromLocalStorage(){ const p = new Promise((res, rej)=>{     const cognitoUser = userPool.getCurrentUser();     if (cognitoUser != null) {         cognitoUser.getSession(function(err, session) {             if (err) {                rej(err)                return             }             localStorage.setItem('user_token', session.getAccessToken().getJwtToken());             const loginsObj = {                 [USERPOOL_ID] : session.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(buildUserObject(cognitoUser))             })         });     }else{      rej('Failed to retrieve user from localStorage')     } }) return p}

So what’s happening here? First we create a CognitoUser object using the userPool imported from aws_profile.js. However, this time we are using the getCurrentUser() function which will pull from previous session memory — a useful feature that AWS Cognito handles for us! If we receive a non-null value back from getCurrentUser(), then we can assume its a valid CognitoUser object and call getSession() to access the latest session variables. The variable we care about is the JWT token in session.getAccessToken().getJwtToken() which we will save to localStorage and place in our loginsObj (Recall from Part 2, Sign In). This will register our login to Federated Identities. Now all we have to do is use that to set our AWS credentials and refresh them before we call buildUserObject() and return it to the React-Redux app. With the userProfileObject that is returned from buildUserObject(), we are logged in!

那么这是怎么回事? 首先,我们创建一个CognitoUser使用对象userPool进口aws_profile.js 。 但是,这一次,我们正在使用getCurrentUser()函数,该函数将从以前的会话内存中拉出-AWS Cognito为我们处理的一项有用功能! 如果我们从getCurrentUser()返回一个非空值,则可以假定其为有效的CognitoUser对象,然后调用getSession()来访问最新的会话变量。 我们关心的变量是session.getAccessToken().getJwtToken()的JWT令牌,我们将其保存到localStorage并将其放置在我们的loginsObj (从第2部分,登录中调用)。 这会将我们的登录名注册到联合身份。 现在,我们要做的就是使用它来设置我们的AWS凭证并刷新它们,然后再调用buildUserObject()并将其返回到React-Redux应用程序。 使用从buildUserObject()返回的userProfileObject ,我们已经登录!

后端验证 (Backend Authentication)

Alright, so all this front-end stuff is great, but to have the complete package we need backend authentication too. Let’s say we have a resource in our backend that we only want to show to logged in users. To request that resource, we will not use a email+password because that would be insecure sending the password for each request. Instead we will use the JWT token that Cognito supplied to us. We simply decrypt the token on the backend and check it against Cognito token references. Let’s walk through how to do that as a general process:

好的,因此所有这些前端功能都很棒,但是要拥有完整的程序包,我们也需要后端身份验证。 假设我们在后端有一个资源,只想显示给登录用户。 要请求该资源,我们将不使用电子邮件+密码,因为那样发送每个请求的密码都是不安全的。 相反,我们将使用Cognito提供给我们的JWT令牌。 我们只需在后端解密令牌,然后根据Cognito令牌引用对其进行检查。 让我们逐步介绍如何作为一般过程进行操作:

I have included the code for this backend written in NodeJS, but the general process works with any backend. See /Bonus_Backend/ for the code. Now let’s start the process at the frontend (/App/).

我已经包含了用NodeJS编写的该后端的代码,但是常规过程适用于任何后端。 有关代码,请参见/Bonus_Backend/ 。 现在,让我们在前端( /App/ )开始该过程。

First we send the JWT token from our client front-end in a HTTP header. The library we use for http requests is axios, but you can use any that you like as long as you know how to include a header attribute. In axios, you simply put an object with a headers key-value as the 3rd argument to the POST function. Find this in action at App/src/api/myAPI.js.

首先,我们从客户端前端通过HTTP标头发送JWT令牌。 我们用于http请求的库是axios ,但是您可以使用任何喜欢的库,只要您知道如何包含标头属性即可。 在axios中,您只需将带有标题键值的对象作为POST函数的第三个参数即可。 可以在App/src/api/myAPI.js

const API_URL = '24.74.347.34' // your backend IP
export function getBackendResource(){  const jwtConfig = {headers: {"jwt": localStorage.getItem("user_token")}}   const p = new Promise((res, rej)=>{    axios.post(API_URL+"/auth_test", null, jwtConfig)     .then((data)=>{      res(data.data)     })     .catch((err)=>{       rej(err)     })   })   return p}

Next we must download the JWT set that AWS Cognito provides for us. Go to the Cognito page in the AWS console and find your region (eg. us-east-1) and your userPoolId (eg. us-east-1_Fa9dl8sWt). Now use them to replace the below placeholders, and follow the link.

接下来,我们必须下载AWS Cognito为我们提供的JWT集。 转到AWS控制台中的Cognito页面,然后找到您的区域(例如us-east-1 )和您的userPoolId(例如us-east-1_Fa9dl8sWt )。 现在,使用它们替换以下占位符,并点击链接。

https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json

You should arrive at a page with text like this:

您应该到达一个包含以下文本的页面:

{     "keys":[        {           "alg":"RS256",         "e":"AQAB",         "kid":"I7Kw/O0QymLQ8A0pPaXNcv5je7BNYXMCW1HdziUTyrQ=",         "kty":"RSA",         "n":"uIqZqU64ytLpQr3J86NMpjxZBRubRzovkQv22oAeHoxO_w4EZuvEeodCV7WxVatHwcVyH0VrkRsqcoigajJO5Xz3s-Ttz_ozhE8wP-BI3DUPOUNtGiKZirNLf9jluScrCUsyyim2UrF4ub-hsxGSt32GFRMfqrkvz0Ral4K4oeIiBNnX8cu_pbSlDgriBLAh8ago41XhqqSFtWwlP-x_KHJc13RBgETj7HOfEm5tr6ibJlMazL3FOoXehfXQw9Yr0752A2hTKAB8reUJXuAwcyTUa8ZEO6IcnhQiaPmIgltxdm-SHdoPqwR_SQxYzZfQzU9uE78ogWT-xP29Gr08Xw",         "use":"sig"      },      {           "alg":"RS256",         "e":"AQAB",         "kid":"fxyn6hg0ziTNer+mBzqmxqGe38uh4neQPorXo3GAa/s=",         "kty":"RSA",         "n":"hMAECS0ALyFaP7OY4ZN5SXqPpkKOdp_RfNAmeCXhK98rmEnD_9Zzqb5oVviZZoqQ5xEZQBRR7a2JOZxL_JZWX7ObteHMSfNZywk8E9FN4XPMJxStZk5JSceKBd5SPYdLzTR58LFMg4OKONA5aJ1sYUu11zq6yMdUBvEJlwBjBrH4lfSkJ_jg4zSeKxsRcM72oAQ_yCnzO5giPoMjyY8VtqCj7NW_7njyQ-bD1WiGaNCkgBxWwYL_13zCxMJxNopa2vHoca0xn9bct-ysS8zIaB3DjNo_8-GGp_HJ4kNW0TczcILtl4mrl81srGzulvuK-mGF0T31IDY-tZWS3IgQYQ",         "use":"sig"      }   ]}

Save this as its own file jwt_set.json in your backend (Example_Backend/App/api/jwt_set.json) so that it can be referenced by your authentication process. The authentication process (function) should run before any protected resource is accessed. The authentication process looks like this code found at Example_Backend/App/api/authCheck.js:

在您的后端( Example_Backend/App/api/jwt_set.json )中将其另存为自己的文件jwt_set.json ,以便您的身份验证过程可以引用它。 身份验证过程(功能)应在访问任何受保护的资源之前运行。 身份验证过程类似于在Example_Backend/App/api/authCheck.js找到的以下代码:

const jwt = require('jsonwebtoken');const jwkToPem = require('jwk-to-pem');const jwt_set = require('./jwt_set.json')
const userPool_Id = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_6i5p2Fwao"
const pems = {}for(let i = 0; i<jwt_set.keys.length; i++){ const jwk = {  kty: jwt_set.keys[i].kty,  n: jwt_set.keys[i].n,  e: jwt_set.keys[i].e } // convert jwk object into PEM const pem = jwkToPem(jwk) // append PEM to the pems object, with the kid as the identifier pems[jwt_set.keys[i].kid] = pem}
exports.authCheck = function(req, res, next){ const jwtToken = req.headers.jwt ValidateToken(pems, jwtToken)   .then((data)=>{    console.log(data)    next()   })   .catch((err)=>{    console.log(err)    res.send(err)   })}
function ValidateToken(pems, jwtToken){ const p = new Promise((res, rej)=>{  const decodedJWT = jwt.decode(jwtToken, {complete: true})  // reject if its not a valid JWT token  if(!decodedJWT){   console.log("Not a valid JWT token")   rej("Not a valid JWT token")  }  // reject if ISS is not matching our userPool Id  if(decodedJWT.payload.iss != userPool_Id){   console.log("invalid issuer")   rej({    message: "invalid issuer",    iss: decodedJWT.payload   })  }  // Reject the jwt if it's not an 'Access Token'  if (decodedJWT.payload.token_use != 'access') {         console.log("Not an access token")         rej("Not an access token")     }     // Get jwtToken `kid` from header  const kid = decodedJWT.header.kid  // check if there is a matching pem, using the `kid` as the identifier  const pem = pems[kid]  // if there is no matching pem for this `kid`, reject the token  if(!pem){   console.log('Invalid access token')   rej('Invalid access token')  }  console.log("Decoding the JWT with PEM!")  // verify the signature of the JWT token to ensure its really coming from your User Pool  jwt.verify(jwtToken, pem, {issuer: userPool_Id}, function(err, payload){   if(err){    console.log("Unauthorized signature for this JWT Token")    rej("Unauthorized signature for this JWT Token")   }else{    // if payload exists, then the token is verified!    res(payload)   }  }) }) return p}

Ok it’s a bit long, we lets break this down one-by-one. First we import the nodeJS dependencies we want and install them.

好的,它有点长,我们让它一一分解。 首先,我们导入所需的nodeJS依赖项并进行安装。

$ npm install jsonwebtoken --save$ npm install jwk-to-pem --save

And then we include the jwt_set.json as well as our Identity Pool Id. That’s 3 dependencies and 1 constant.

然后,我们包括jwt_set.json以及我们的身份池ID。 那是3个依赖关系和1个常量。

const jwt = require('jsonwebtoken');const jwkToPem = require('jwk-to-pem');const jwt_set = require('./jwt_set.json')
const userPool_Id = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_6i5p2Fwao"

We create 1 more constant called pems, which is created by the for loop executed upon loading of the file.

我们再创建1个称为pems常量,该常量由加载文件时执行的for循环创建。

const pems = {}for(let i = 0; i<jwt_set.keys.length; i++){ // take the jwt_set key and create a jwk object for conversion into PEM const jwk = {  kty: jwt_set.keys[i].kty,  n: jwt_set.keys[i].n,  e: jwt_set.keys[i].e } // convert jwk object into PEM const pem = jwkToPem(jwk) // append PEM to the pems object, with the kid as the identifier pems[jwt_set.keys[i].kid] = pem}

At a high level, what is happening is that for each key in jwt_set.json, we are creating a PEM object to be the value of that jwt_set key’s kid. We don’t need to know exactly what is happening, but basically we are creating a PEM that can be used to match against the jwt kid coming from the header of incoming http requests as a means of verifying authentication. If you feel like you need to know exactly what these terms mean, check out The Anatomy of a JSON Web Token by Chris Sevilleja.

从高层次jwt_set.json ,正在发生的事情是,对于jwt_set.json中的每个键,我们正在创建一个PEM对象作为该jwt_set键的kid的值。 我们不需要确切知道发生了什么,但是基本上,我们正在创建一个PEM ,可以将其与来自传入的HTTP请求的标头的jwt kid相匹配,作为验证身份验证的一种方法。 如果您觉得需要确切了解这些术语的含义,请查看Chris Sevilleja撰写的《 JSON Web令牌的剖析》

Anyways, moving on we have the authCheck function that validates the incoming jwt token from the header using ValidateToken(). Pretty simple.

无论如何,我们继续使用authCheck函数,该函数使用ValidateToken()来自标头的传入jwt令牌。 很简单

exports.authCheck = function(req, res, next){ const jwtToken = req.headers.jwt ValidateToken(pems, jwtToken)   .then((data)=>{    console.log(data)    next()   })   .catch((err)=>{    console.log(err)    res.send(err)   })}

authCheck() is used elsewhere in your backend as the function called before accessing a protected resource. For a NodeJS Express backend, it would look like this:

authCheck()在后端的其他地方用作访问受保护资源之前调用的函数。 对于NodeJS Express后端,它看起来像这样:

const authCheck = require('./api/authCheck').authCheck
// auth routeapp.get('/auth_test', authCheck, function(req, res, next){ console.log("Passed the auth test!") res.send("Nice job! Your token passed the auth test!")});

Finally let’s look at ValidateToken(), which can be broken down into 6 smaller parts. Pass in the pems constant and the jwt from the http request header. Now follow the 6 steps of the validation process and if all pass, the JWT token is accepted! The promise will be resolved and authCheck() will allow access to our protected resource.

最后,让我们看一下ValidateToken() ,它可以分为6个较小的部分。 从http请求标头中pems常量和jwt 。 现在执行验证过程的6个步骤,如果全部通过,则接受JWT令牌! 该承诺将被解决,并且authCheck()将允许访问我们受保护的资源。

function ValidateToken(pems, jwtToken){ const p = new Promise((res, rej)=>{
// PART 1: Decode the JWT token  const decodedJWT = jwt.decode(jwtToken, {complete: true})
// PART 2: Check if its a valid JWT token  if(!decodedJWT){   console.log("Not a valid JWT token")   rej("Not a valid JWT token")  }
// PART 3: Check if ISS matches our userPool Id  if(decodedJWT.payload.iss != userPool_Id){   console.log("invalid issuer")   rej({    message: "invalid issuer",    iss: decodedJWT.payload   })  }
// PART 4: Check that the jwt is an AWS 'Access Token'  if (decodedJWT.payload.token_use != 'access') {     console.log("Not an access token")     rej("Not an access token")  }
// PART 5: Match the PEM against the request KID  const kid = decodedJWT.header.kid  const pem = pems[kid]  if(!pem){   console.log('Invalid access token')   rej('Invalid access token')  }  console.log("Decoding the JWT with PEM!")
// PART 6: Verify the signature of the JWT token to ensure its really coming from your User Pool  jwt.verify(jwtToken, pem, {issuer: userPool_Id}, function(err, payload){   if(err){    console.log("Unauthorized signature for this JWT Token")    rej("Unauthorized signature for this JWT Token")   }else{    // if payload exists, then the token is verified!    res(payload)   }  }) }) return p}

So that’s our backend auth checker. To integrate it, go to Example_Backend/App/router.js where we receive incoming http requests.

这就是我们的后端身份验证检查器。 要集成它,请转到Example_Backend/App/router.js ,在此我们接收传入的http请求。

// routesconst Authentication = require('./routes/auth_routes');
// router middlewearconst authCheck = require('./api/authCheck').authCheck
module.exports = function(app){ // Auth related routes app.get('/auth_test', authCheck, Authentication.authtest);}

All we have to do is add it as the 2nd argument to our ExpressJS route. If you have a different backend, the same general process applies.

我们要做的就是将它添加为ExpressJS路由的第二个参数。 如果后端不同,则适用相同的常规过程。

And that’s it, backend authentication using our same AWS Cognito environment. How powerful!

就是这样,使用我们相同的AWS Cognito环境进行后端身份验证。 多么强大!

结论 (Conclusion)

Congratulations for following this long tutorial on AWS Cognito and Federated Identities! By completing this to the end, you can now enjoy top-notch user management designed by the world’s largest cloud services provider. Much of the functionality you receive with AWS would take weeks and lots of expert knowledge to implement if you were to make a custom system. Nor is there any guarantee you would implement a custom user management system well, without exposing your users to security flaws and holes. By using Amazon, you can rest at night knowing all that is taken care of for you by a multi-billion dollar internet company.

恭喜您关注了这份有关AWS Cognito和联合身份的漫长教程! 通过完成此操作,您现在可以享受由全球最大的云服务提供商设计的一流的用户管理。 如果要创建定制系统,AWS收到的许多功能将需要数周的时间才能实现,并且要具备大量的专业知识。 也没有任何保证可以很好地实现自定义用户管理系统,而又不会使用户暴露于安全漏洞和漏洞中。 通过使用亚马逊,您可以在夜晚休息,知道数十亿美元的互联网公司为您提供的一切服务。

So there we have it: A complete user management system ready for real-world use. If you think you benefited or learned a lot from this tutorial series, please share and subscribe! I will be publishing more practical AWS tutorials in the coming months so be sure to stay tuned. See you next time!

因此,我们有了它:可供实际使用的完整用户管理系统。 如果您认为自己从本教程系列中学到了很多东西,请分享并订阅! 在接下来的几个月中,我将发布更多实用的AWS教程,因此请务必继续关注。 下次见!

Main Table of Contents Click Here

主要目录请点击这里

Part A: Initial Setup

A部分: 初始设置

Part B: The Core Functionality

B部分: 核心功能

Part C: Last Steps to Full Fledged

C部分: 全面完成的最后步骤

These methods were partially used in the deployment of renthero.ca

这些方法部分地用于了renthero.ca的部署中

翻译自: https://www.freecodecamp.org/news/user-management-with-aws-cognito-3-3-last-steps-to-full-fledged-73f4a3a9f05e/

aws cognito

 类似资料: