Angular 6如何在每次请求中维持JWT会话?
解决Angular 6 JWT令牌自动过期且不刷新的问题
嘿,我看你遇到了JWT会话维持的核心问题——当前的AuthInterceptor只是把本地存储的令牌附在请求上,但完全没处理令牌过期后的自动刷新,也没有在每次请求时重置令牌的有效期。咱们一步步来搞定这个:
1. 先确认后端的必要支持
首先你的NodeJS API需要实现刷新令牌(Refresh Token)机制:
- 用户登录成功时,除了返回30秒过期的
accessToken,还要返回一个有效期更长的refreshToken(比如1天) - 提供一个专门的刷新接口(比如
/api/refresh-token),接收refreshToken,验证通过后返回新的accessToken(可选更新refreshToken)
2. 改造你的AuthInterceptor
我们需要在Interceptor里加入:令牌过期判断、自动刷新逻辑、并发请求处理(避免多次调用刷新接口)。
首先添加解析JWT过期时间的辅助函数,再修改拦截逻辑:
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http'; import { Observable, Subject, throwError } from 'rxjs'; import { Router } from '@angular/router'; import { HttpClient } from '@angular/common/http'; import { tap, switchMap, filter, take, catchError } from 'rxjs/operators'; @Injectable() export class AuthInterceptor implements HttpInterceptor { // 标记是否正在刷新令牌,避免并发请求重复刷新 private refreshTokenInProgress = false; // 用来通知等待中的请求新令牌已获取 private refreshTokenSubject: Subject<any> = new Subject<any>(); constructor(private router: Router, private http: HttpClient) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 跳过不需要认证的请求 if (req.headers.get('No-Auth') === "True") { return next.handle(req.clone()); } const userToken = localStorage.getItem('userToken'); const refreshToken = localStorage.getItem('refreshToken'); // 没有令牌直接跳登录 if (!userToken) { this.router.navigateByUrl('/login'); return next.handle(req); } // 检查令牌是否即将过期(提前5秒判断,避免请求途中过期) if (this.isTokenExpired(userToken)) { if (!this.refreshTokenInProgress) { this.refreshTokenInProgress = true; this.refreshTokenSubject.next(null); // 调用后端刷新令牌接口 return this.http.post('/api/refresh-token', { refreshToken }) .pipe( switchMap((response: any) => { this.refreshTokenInProgress = false; // 更新本地存储的新令牌 localStorage.setItem('userToken', response.accessToken); // 通知等待的请求可以继续了 this.refreshTokenSubject.next(response.accessToken); // 用新令牌克隆原请求并发送 const clonedReq = req.clone({ headers: req.headers.set("Authorization", `Bearer ${response.accessToken}`) }); return next.handle(clonedReq); }), catchError((error) => { this.refreshTokenInProgress = false; // 刷新失败,清除令牌跳登录 localStorage.removeItem('userToken'); localStorage.removeItem('refreshToken'); this.router.navigateByUrl('/login'); return throwError(error); }) ); } else { // 如果正在刷新,等待新令牌获取后再处理当前请求 return this.refreshTokenSubject.pipe( filter(token => token !== null), take(1), switchMap(token => { const clonedReq = req.clone({ headers: req.headers.set("Authorization", `Bearer ${token}`) }); return next.handle(clonedReq); }) ); } } else { // 令牌有效,正常发送请求 const clonedReq = req.clone({ headers: req.headers.set("Authorization", `Bearer ${userToken}`) }); return next.handle(clonedReq) .pipe( tap(event => { if (event instanceof HttpResponse) { // 修正之前的时间计算错误,正确统计请求耗时 const requestStartTime = new Date().getTime(); const elapsed = Date.now() - requestStartTime; console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`); } }, error => { console.error('Request error:', error); // 如果后端返回401,强制跳登录 if (error.status === 401) { localStorage.removeItem('userToken'); this.router.navigateByUrl('/login'); } }) ); } } // 解析JWT获取过期时间 private getTokenExpirationDate(token: string): Date | null { try { // JWT的payload是Base64编码的第二部分 const decodedPayload = JSON.parse(atob(token.split('.')[1])); if (!decodedPayload.exp) return null; const expirationDate = new Date(0); expirationDate.setUTCSeconds(decodedPayload.exp); return expirationDate; } catch (e) { // 解析失败视为令牌无效 return null; } } // 判断令牌是否即将过期 private isTokenExpired(token: string): boolean { const expirationDate = this.getTokenExpirationDate(token); if (!expirationDate) return true; // 提前5秒触发刷新,避免请求刚好在发送时过期 return expirationDate.getTime() - new Date().getTime() < 5000; } }
3. 关键注意事项
- Refresh Token的存储安全:如果你的应用对安全性要求高,建议把refreshToken存在HttpOnly Cookie里(前端无法读取),由后端处理刷新逻辑,避免XSS攻击窃取令牌
- 后端刷新接口的安全:要验证refreshToken的有效性,比如和用户绑定、设置过期时间,避免被滥用
- 并发请求处理:上面的代码用
refreshTokenInProgress和refreshTokenSubject确保多个请求同时发现令牌过期时,只会调用一次刷新接口,其他请求等待新令牌 - 令牌过期后的降级处理:如果refreshToken也过期了,直接清除本地存储并跳转登录页面
内容的提问来源于stack exchange,提问作者Bala
相关产品推荐
相关产品推荐

