Angular技术问询:如何通过认证拦截器刷新JWT令牌并续期token_b
Angular认证拦截器:自动处理token_b过期刷新方案
嘿,我来帮你搞定这个token自动刷新的问题!你的场景我完全get:用户登录拿到token_a,所有API请求都要用token_a生成的token_b,拦截器已经能给请求加token_b,但token_b有效期短,得在它过期的时候自动重新生成,对吧?下面是完善后的拦截器实现,附带详细逻辑说明:
完整的auth.interceptor.ts代码
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpErrorResponse } from '@angular/common/http'; import { Observable, BehaviorSubject, throwError } from 'rxjs'; import { catchError, filter, take, switchMap } from 'rxjs/operators'; import { AuthService } from './auth.service'; // 假设你有专门的AuthService管理token @Injectable() export class AuthInterceptor implements HttpInterceptor { // 标记是否正在刷新token,避免并发请求重复刷新 private isRefreshing = false; // 用于通知等待中的请求刷新完成 private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null); constructor(private authService: AuthService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { let authRequest = request; const currentTokenB = this.authService.getTokenB(); if (currentTokenB) { // 先检查token_b是否过期 if (!this.isTokenExpired(currentTokenB)) { // 没过期就直接把token_b加到请求头 authRequest = this.attachTokenToRequest(request, currentTokenB); } else { // 过期了就走刷新流程 return this.handleTokenRefresh(request, next); } } return next.handle(authRequest).pipe( catchError((error: HttpErrorResponse) => { // 如果请求返回401(未授权),说明token_b可能过期了(比如本地时间不准导致提前检查失效),再试一次刷新 if (error.status === 401 && !request.url.includes('/refresh-token-b')) { return this.handleTokenRefresh(request, next); } return throwError(() => error); }) ); } // 把token_b附加到请求头的工具方法 private attachTokenToRequest(request: HttpRequest<any>, token: string): HttpRequest<any> { return request.clone({ setHeaders: { Authorization: `Bearer ${token}` // 这里根据你的后端要求调整格式,比如有的是Token xxx } }); } // 检查token_b是否过期的方法 private isTokenExpired(token: string): boolean { // 解析JWT的payload部分,注意如果你的token不是JWT格式,要改成对应的解析逻辑 const payload = JSON.parse(atob(token.split('.')[1])); // exp字段是秒级时间戳,转成毫秒和当前时间对比 const expirationTime = payload.exp * 1000; return Date.now() > expirationTime; } // 处理token_b刷新的核心逻辑 private handleTokenRefresh(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); // 调用AuthService里的方法,用token_a去后端请求新的token_b return this.authService.refreshTokenB().pipe( switchMap((newTokenB: string) => { this.isRefreshing = false; this.refreshTokenSubject.next(newTokenB); // 更新本地存储的token_b this.authService.setTokenB(newTokenB); // 用新的token_b重新发起原来的请求 return next.handle(this.attachTokenToRequest(request, newTokenB)); }), catchError((error) => { this.isRefreshing = false; // 如果刷新失败,一般是token_a也过期了,直接跳登录页 this.authService.logout(); return throwError(() => error); }) ); } else { // 如果已经在刷新token了,就让后续请求等着,拿到新token后再继续 return this.refreshTokenSubject.pipe( filter(token => token !== null), take(1), switchMap(token => { return next.handle(this.attachTokenToRequest(request, token)); }) ); } } }
关键逻辑说明
- 避免并发刷新:用
isRefreshing标志和BehaviorSubject,防止多个请求同时触发token刷新,减少不必要的API调用。 - 双重过期检查:提前在前端检查token是否过期,同时加上401返回后的兜底刷新,避免因为本地时间偏差导致的判断失误。
- 依赖AuthService:你需要自己实现
AuthService里的几个核心方法:getTokenB():从本地存储(比如localStorage/sessionStorage)获取当前token_bsetTokenB():把新的token_b存到本地refreshTokenB():调用后端API,用token_a换取新的token_blogout():清除本地token,跳转到登录页
注册拦截器
最后别忘了在app.module.ts里把拦截器注册到Angular的HTTP拦截器链中:
import { NgModule } from '@angular/core'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthInterceptor } from './auth.interceptor'; @NgModule({ imports: [HttpClientModule], providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true // 允许多个拦截器 } ] }) export class AppModule {}
内容的提问来源于stack exchange,提问作者Kay
相关产品推荐
相关产品推荐

