Angular中的JWT身份验证

angular jwt

In my last article, JWT Auth in ASP.NET Core, we talked about the implementation of JWT in the back-end. To follow up, this article will focus on the front-end part of the JWT story. You can find the front-end source code from the same GitHub repository as the back-end part.

在上一篇文章ASP.NET Core中的JWT Auth中 ,我们讨论了JWT在后端的实现。 作为后续,本文将重点介绍JWT故事的前端部分。 您可以从与后端部分相同的GitHub存储库中找到前端源代码。

To make JWT authentication work, the front-end application at least operates in the following scenes:

为了使JWT身份验证有效,前端应用程序至少在以下情况下运行:

  1. Displays a login form, and sends user credentials to the back-end service to get user’s claims, a JWT access token, and a refresh token.

    显示登录表单,并将用户凭证发送到后端服务以获取用户的声明,JWT访问令牌和刷新令牌。
  2. Stores the JWT access token and refresh token in a browser’s localStorage, so that the application in different browser tabs can use the same tokens.

    将JWT访问令牌和刷新令牌存储在浏览器的localStorage中,以便位于不同浏览器选项卡中的应用程序可以使用相同的令牌。
  3. Adds an authorization header when sending HTTP requests.

    发送HTTP请求时添加授权标头。
  4. Tracks the expiration time of the access token and sends a request to refresh tokens when the access token is about to expire.

    跟踪访问令牌的到期时间,并在访问令牌即将到期时发送刷新令牌的请求。
  5. Removes the tokens from localStorage when the user logs out.

    用户注销时,从localStorage删除令牌。

Today, we will build a simple app using Angular. We will implement an AuthService class to handle login, logout, and refresh token processes, as well as operations for localStorage key-value pairs. We will create a JwtInterceptor class to add a JWT Bearer token to the HTTP request headers, and an UnauthorizedInterceptor class to redirect the user to the login page if an HTTP status code 401 is received. We will use an AuthGuard to prevent unauthenticated users from visiting the application pages.

今天,我们将使用Angular构建一个简单的应用程序。 我们将实现AuthService类来处理登录,注销和刷新令牌过程,以及localStorage键值对的操作。 我们将创建一个JwtInterceptor类,以将JWT Bearer令牌添加到HTTP请求标头,以及一个UnauthorizedInterceptor类,以在收到HTTP状态代码401的情况下将用户重定向到登录页面。 我们将使用AuthGuard来防止未经AuthGuard验证的用户访问应用程序页面。

If you pull the code from my GitHub repository, then you can run the demo application on Linux Docker containers. Or, if you want, you can run the Angular app and the ASP.NET Core app separately. The following screen recording shows a demo of this app.

如果从GitHub存储库中提取代码,则可以在Linux Docker容器上运行演示应用程序。 或者,如果需要,可以分别运行Angular应用程序和ASP.NET Core应用程序。 以下屏幕录像显示了此应用程序的演示。

Image for post

Now let’s dive into the code.

现在,让我们深入研究代码。

auth.service.ts (auth.service.ts)

Let’s use Angular CLI to generate an auth.service.ts file. I find that this AuthService class is a little bit lengthy, so I have decided to first paste the skeleton of the AuthService class, then I will explain its methods.

让我们使用Angular CLI生成一个auth.service.ts文件。 我发现该AuthService类有点长,因此我决定先粘贴AuthService类的框架,然后再解释其方法。

In the code above, the first three lines are the default decorator for an Angular service, which means that the service will be available globally for dependency injection.

在上面的代码中,前三行是Angular服务的默认装饰器,这意味着该服务将在全局范围内用于依赖项注入。

Line 5 sets the back-end API URL, which is configured in the environment.ts file and it will talk to the Account controller in the ASP.NET Core web API project.

第5行设置了后端API URL,该URL在environment.ts文件中配置,它将与ASP.NET Core Web API项目中的Account控制器进行通信。

Line 6 refers to a private field timer, which is used in two private methods startTokenTimer() and stopTokenTimer(). The startTokenTimer() method is called inside the login() method and the refreshToken() method. We use rxjs observables to track the access token’s lifetime, so that when the token is about to expire, the timer will trigger the refreshToken() method to exchange a new set of tokens. On the other hand, when the logout() method is called, the stopTokenTimer() method will halt the timer.

第6行涉及一个专用字段timer ,该timer在两个专用方法startTokenTimer()stopTokenTimer() 。 所述startTokenTimer()方法被调用内部login()方法和refreshToken()方法。 我们使用rxjs observables来跟踪访问令牌的生存期,以便在令牌即将过期时, timer将触发refreshToken()方法来交换一组新的令牌。 另一方面,当调用logout()方法时, stopTokenTimer()方法将暂停timer

Lines 7 and 8 show a common pattern in Angular for sharing the user’s state. The user$ observable can be broadcast to all observers in services, components, guards, interceptors, and so on. Note that I don’t save the user’s state in the browser localStorage, so that end-users are not able to modify its value. Using the user$ observable can guarantee an immutable user state, which may contain information about user permissions and other claims.

第7和8行显示了Angular中用于共享用户状态的通用模式。 可观察的user$可以广播给服务,组件,警卫,拦截器等中的所有观察者。 请注意,我不会在浏览器的localStorage中保存用户的状态,因此最终用户将无法修改其值。 使用user$ observable可以保证用户状态不变,其中可能包含有关用户权限和其他声明的信息。

Lines 10 to 20 are optional touch-ups. We define a storageEventListener which will watch the value change events in the browser’s localStorage when the application starts, and the event listener will be removed when the application terminates. We need this global event listener because we want all browser tabs to sync with the user’s information upon login and logout events. As a result, if a user logs in the app from a browser tab, then the other tabs will also reflect the login status. Similarly, if a user logs out of the app from a tab, then all other tabs will be logged out as well. Most of online articles or tutorials miss this feature.

第10到20行是可选的修饰。 我们定义了一个storageEventListener ,它将在应用程序启动时监视浏览器的localStorage中的值更改事件,并在应用程序终止时删除事件侦听器。 我们需要此全局事件侦听器,因为我们希望所有浏览器选项卡在登录和注销事件时与用户信息同步。 因此,如果用户从浏览器选项卡登录应用程序,则其他选项卡也将反映登录状态。 同样,如果用户从某个选项卡中注销该应用程序,则所有其他选项卡也将被注销。 大多数在线文章或教程都缺少此功能。

The following code snippet shows some more implementation details.

以下代码段显示了更多实现细节。

The storageEventListener (lines 1 to 10) monitors the value changes for the login-event and the logout-event which are dispatched in the login() and logout() methods, respectively. When a user logs out, then other tabs will have a null user, which could invalidate those sessions. When a user logs in, then other tabs will reload their current pages which are bonded with new session parameters. Line 19 is an example for dispatching the login-event value change events.

storageEventListener (第1行到第10行)监视分别在login()logout()方法中分派的login-eventlogout-event的值更改。 当用户注销时,其他选项卡将具有null用户,这会使这些会话无效。 当用户登录时,其他选项卡将重新加载其当前页面,这些页面与新的会话参数绑定在一起。 第19行是用于分配login-event值更改事件的示例。

The login() method takes the username and password, and passes them to the login API endpoint. Upon a successful login, line 17 emits a new value to the _user subject, so that all observers will get its latest value. Line 18 saves the access token and the refresh token in a browser’s localStorage, so that the tokens can be shared across browser tabs or windows.

login()方法采用用户名和密码,并将其传递给登录API端点。 成功登录后,第17行向_user主题发出新值,以便所有观察者都将获得其最新值。 第18行将访问令牌和刷新令牌保存在浏览器的localStorage中,以便可以在浏览器选项卡或窗口之间共享令牌。

Line 20 executes the startTokenTimer() method, which starts a timer to count down. The getTokenRemainingTime() method computes the access token’s expiration time by decoding the access token. The timer runs until the JWT access token is about to expire, then the timer calls the refreshToken() method to refresh the tokens. If your app is a highly sensitive website, you may want to stop refresh tokens after certain amount of times, then you can create another key-value pair in localStorage to track it.

第20行执行startTokenTimer()方法,该方法启动一个计时器进行倒计时。 getTokenRemainingTime()方法通过解码访问令牌来计算访问令牌的到期时间。 计时器一直运行到JWT访问令牌即将到期为止,然后计时器调用refreshToken()方法刷新令牌。 如果您的应用程序是一个高度敏感的网站,则可能需要在一定时间后停止刷新令牌,然后可以在localStorage中创建另一个键值对以对其进行跟踪。

I will omit the code for logout() and refreshToken() methods for simplicity. Let’s move on to the AuthGuard.

为了简单起见,我将省略logout()refreshToken()方法的代码。 让我们继续进行AuthGuard

auth.guard.ts (auth.guard.ts)

We use Angular CLI to generate a guard which controls the access of desired routes. In this demo app, we implement the canActivate method which listens to the user$ observable in the AuthService class, so that if the user$ observable emits a null value, then route navigation will end up at the login page. The following code snippet shows an example implementation of the AuthGuard class.

我们使用Angular CLI生成一个保护措施,该保护措施控制所需路由的访问。 在这个演示程序中,我们实施canActivate监听的方法user$可观察AuthService类,因此,如果user$观察到发出null值,那么路线导航将在登录页面结束。 下面的代码片段显示了AuthGuard类的示例实现。

Then in the app-routing.module.ts file, we can protect some routes using the canActivate lifecycle hook like below.

然后,在app-routing.module.ts文件中,我们可以使用canActivate生命周期钩子来保护某些路由,如下所示。

jwt.interceptor.tsunauthorized.interceptor.ts jwt.interceptor.ts (jwt.interceptor.ts and unauthorized.interceptor.ts)

We need an HTTP interceptor to add an authorization header, so that all requests sent to the back-end API endpoints will have the access token for identity purposes. Angular CLI can easily generate the interceptor’s skeleton for us. We simply need to clone the original HTTP request, and attach the Bearer token to the Authorization header. The following code snippet shows an example implementation of the JwtInterceptor class.

我们需要一个HTTP拦截器来添加授权标头,以便发送到后端API端点的所有请求都将具有用于身份目的的访问令牌。 Angular CLI可以轻松为我们生成拦截器的框架。 我们只需要克隆原始的HTTP请求,并将Bearer令牌附加到Authorization标头即可。 下面的代码片段显示了JwtInterceptor类的示例实现。

If a request returns an HTTP status code 401, then it means the current user’s identity is no longer permitted to the resource, so we should redirect the user to the login page. We can use another HttpInterceptor to deal with the 401 responses. An example UnauthorizedInterceptor class is shown below.

如果请求返回HTTP状态代码401,则意味着资源不再允许当前用户的身份,因此我们应将用户重定向到登录页面。 我们可以使用另一个HttpInterceptor处理401响应。 下面显示了一个示例UnauthorizedInterceptor类。

In a production-ready app, we may need to implement another service to gracefully handle all errors. We will not discuss that in this article.

在可用于生产环境的应用程序中,我们可能需要实施另一项服务以妥善处理所有错误。 我们将不在本文中讨论。

app-initializer.tscore.module.ts (app-initializer.ts and core.module.ts)

It is a good practice to refresh tokens when the app is first loaded in a browser tab, in order to improve user experience. To do that, we write an appInitializer function like below.

最好在首次将应用程序加载到浏览器选项卡中时刷新令牌,以改善用户体验。 为此,我们编写了一个如下的appInitializer函数。

The appIntializer, JwtInterceptor, and UnauthorizedInterceptor are registered in an Angular module as follows.

appIntializerJwtInterceptorUnauthorizedInterceptor如下所示在Angular模块中注册。

Finally, we can import the CoreModule into the AppModule, so that the three providers above can work globally.

最后,我们可以将CoreModule导入AppModule ,以便上述三个提供程序可以全局工作。

I think I have touched all the bases for implementing our Angular app. We can try out the app using the ng serve command in Angular CLI, after we start the ASP.NET Core web API app. Please also try the app in two or more browser tabs and play with the login/logout functionalities.

我想我已经触及了实现Angular应用程序的所有基础。 启动ASP.NET Core Web API应用程序后,我们可以使用Angular CLI中的ng serve命令尝试该应用程序。 请同时在两个或多个浏览器标签中尝试该应用,并使用登录/注销功能。

在Docker上使用NGINX服务Angular应用 (Serve the Angular App with NGINX on Docker)

I also include a Dockerfile for the Angular app, so that it can be served by an NGINX server in a Docker container. I have written another article, Get Started with NGINX on Docker, which talks about the configurations of Docker and NGINX, so I won’t repeat them here.

我还为Angular应用程序提供了一个Dockerfile ,以便它可以由Docker容器中的NGINX服务器提供服务。 我还写了另一篇文章Docker上的NGINX入门 ,它讨论了Docker和NGINX的配置,因此在此不再赘述。

To echo the beginning of this article, we can also run the app using Docker Compose, so that both the back-end app and the front-end app can run simultaneously.

为了回应本文的开头,我们还可以使用Docker Compose运行该应用程序,以便后端应用程序和前端应用程序可以同时运行。

结论 (Conclusion)

That’s all for today. We have implemented an Angular app with JWT authentication, and you can play with it on multiple browser tabs/windows. Again, the complete solution is in my GitHub repository. Hope it helps. Thanks for reading.

今天就这些。 我们已经实现了具有JWT身份验证的Angular应用,您可以在多个浏览器标签/窗口上使用它。 同样,完整的解决方案在我的GitHub存储库中 。 希望能帮助到你。 谢谢阅读。

翻译自: https://codeburst.io/jwt-authentication-in-angular-48cfa882832c

angular jwt

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值