Angular如何无需用户操作自动检测JWT过期并获取新JWT和刷新令牌
实现无操作自动刷新JWT的两种可行方案
方案1:定时主动检测刷新
在你的AuthService中增加定时任务逻辑,用户登录成功、token刷新成功后,根据JWT的过期时间设置定时任务,提前触发刷新:
- 首先在AuthService中定义定时器变量:
private refreshTokenTimer: any; - 修改
setAuth方法,每次保存新token后计算定时时间:
public setAuth(authToken: AuthToken){ this._storageService.saveToken(authToken.token?.toString()); this._storageService.saveRefreshToken(authToken.refreshToken); // 清除之前的旧定时器 if(this.refreshTokenTimer) clearTimeout(this.refreshTokenTimer); // 解析JWT过期时间 const token = this._storageService.getToken(); const expireTime = this._jwtHelperService.getTokenExpirationDate(token).getTime(); const currentTime = Date.now(); // 提前30秒刷新,可根据需求调整提前量 const delay = expireTime - currentTime - 30 * 1000; if(delay > 0){ this.refreshTokenTimer = setTimeout(async ()=>{ await this.isUserAuthenticated(); }, delay) } }
- 新增登出时清理定时器的逻辑,避免内存泄漏:
public logout(){ if(this.refreshTokenTimer) clearTimeout(this.refreshTokenTimer); this._storageService.clearToken(); this._storageService.clearRefreshToken(); this._router.navigate([RouteConstant.SIGN_IN]); }
这种方案可以实现即使用户无任何页面操作,也能在token快过期时自动刷新。
方案2:HTTP拦截器全局自动刷新(推荐和方案1结合使用)
新增Angular HTTP拦截器,所有后端请求返回401状态码时,自动触发token刷新逻辑,刷新成功后重发原本失败的请求,用户完全无感知:
- 新建
auth-interceptor.ts:
@Injectable() export class AuthInterceptor implements HttpInterceptor { private isRefreshing = false; private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null); constructor(private _authService: AuthService, private _storageService: StorageService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 给请求加Authorization头 const token = this._storageService.getToken(); if (token) { request = request.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); } return next.handle(request).pipe( catchError(error => { if (error.status === 401 && !request.url.includes('refresh')) { return this.handle401Error(request, next); } return throwError(() => error); }) ); } private handle401Error(request: HttpRequest<any>, next: HttpHandler) { if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); return this._authService.refreshAuthToken({ token: this._storageService.getToken(), refreshToken: this._storageService.getRefreshToken() }).pipe( switchMap((success: boolean) => { this.isRefreshing = false; if(success){ this.refreshTokenSubject.next(this._storageService.getToken()); // 重发原请求 return next.handle(request.clone({ setHeaders: { Authorization: `Bearer ${this._storageService.getToken()}` } })); } // 刷新失败跳登录 this._authService.logout(); return throwError(()=> 'Refresh token failed'); }), catchError(err => { this.isRefreshing = false; this._authService.logout(); return throwError(() => err); }) ); } else { return this.refreshTokenSubject.pipe( filter(token => token != null), take(1), switchMap(jwt => { return next.handle(request.clone({ setHeaders: { Authorization: `Bearer ${jwt}` } })); }) ); } } }
- 在
app.module.ts的providers中注册拦截器:
providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ]
额外优化建议
你现有代码中的refreshAuthToken方法判断逻辑有误,原代码if (newToken && newRefreshToken == null)的判断条件不符合预期,建议修改为:
if (!newToken || !newRefreshToken){ return false } else { return true; }
另外建议后端刷新接口增加刷新令牌的有效性校验、单次使用限制,避免刷新令牌被盗用的安全风险。
内容的提问来源于stack exchange,提问作者Shivam Dubey
相关产品推荐
相关产品推荐

