Angular拦截器刷新Token仍触发401错误求助
Access Token过期自动刷新仍出现401错误排查与解决
我尝试在access token过期时自动刷新token,但仍然出现401错误。为测试需求,我将access token过期时间设为1分钟,refresh token设为30天。当access token过期后刷新页面,仍出现401错误,不清楚问题原因。
AuthInterceptor拦截器代码
export class AuthInterceptor implements HttpInterceptor { private isRefresh = false; constructor(private tokenService:TokenService, private authenticationService:AuthenticationService) {} intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { let accessToken = this.tokenService.getAccessToken(); if (accessToken){ request = this.setHeaders(request, accessToken); } return next.handle(request).pipe( catchError((err) =>{ console.log(err.status); console.log(err instanceof HttpErrorResponse) if(err.status === 401 && err instanceof HttpErrorResponse){ console.log("CALLING!!!!!!!!!!!!!") return this.handleAuthorizationError(request, next); } return throwError(()=>err.error.message); }) ); } private setHeaders(request:HttpRequest<any>, token:string){ return request.clone({ setHeaders: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); } private handleAuthorizationError(request:HttpRequest<any>, next:HttpHandler){ console.log("CALLING HANDLE") if(!this.isRefresh){ this.isRefresh = true; return this.authenticationService.refreshToken(this.tokenService.getRefreshToken()).pipe( switchMap((res)=>{ this.isRefresh = false; this.tokenService.setAccessToken(res.accessToken); this.tokenService.setRefreshToken(res.refreshToken); return next.handle(this.setHeaders(request, res.accessToken)); }) ) }else{ return next.handle(request); } } }
TokenService代码
export class TokenService { constructor() { } getAccessToken(){ if(localStorage.getItem("accessToken") == null){ return ""; } return localStorage.getItem("accessToken"); } getRefreshToken(){ if(localStorage.getItem("refreshToken") == null){ return ""; } return localStorage.getItem("refreshToken"); } setAccessToken(token:string){ localStorage.setItem("accessToken", token); } setRefreshToken(token:string){ localStorage.setItem("refreshToken", token); } clearToken(){ localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); } }
AuthenticationService代码
export class AuthenticationService { constructor(private http:HttpClient) { } public authenticate(request:AuthenticationRequest){ return this.http.post<AuthenticationResponse>(`${AUTH_API}/auth/authenticate`, request) .pipe( catchError((err)=>{ console.log("Error handled by Service..." + err.status) return throwError(()=> new Error(err.error.message)); }) ); } public logout(){ return this.http.get(`${AUTH_API}/auth/logout`); } public currentUserAccess():Observable<User>{ return this.http.get<User>(`${AUTH_API}/auth/current-user`) .pipe( catchError((err) => { console.log("Error handled by Service..." + err.status) return throwError(()=> new Error("fix later!")); }) ); } public refreshToken(token:any):Observable<AuthenticationResponse>{ return this.http.post<AuthenticationResponse>(`${AUTH_API}/auth/refresh-token`, { "refreshToken": token }).pipe(catchError((err)=>{ console.log("Error handled by Service..." + err.status) return throwError(()=> new Error(err.error.message)); })); } }
请求与响应接口定义
export interface AuthenticationRequest{ username:string; password:string; } export interface AuthenticationResponse{ accessToken:string, refreshToken:string }
问题排查与解决方案
1. 刷新Token请求被拦截带上过期Token
页面刷新后,Interceptor会直接读取localStorage中已过期的accessToken并添加到所有请求头,包括刷新Token的请求本身,导致刷新请求也返回401。
修复代码:
在Interceptor的intercept方法中,判断请求是否为刷新Token接口,跳过Authorization头添加:
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { let accessToken = this.tokenService.getAccessToken(); // 跳过刷新Token的请求,避免携带过期Token if (accessToken && !request.url.includes('/auth/refresh-token')){ request = this.setHeaders(request, accessToken); } // 后续逻辑保持不变 }
2. 刷新失败后未重置状态与清理Token
当前代码中如果刷新Token请求失败,isRefresh标志位不会重置,导致后续所有请求都无法触发刷新逻辑,也没有清理无效Token跳转到登录页的处理。
修复代码:
在handleAuthorizationError的刷新逻辑中添加错误处理:
return this.authenticationService.refreshToken(this.tokenService.getRefreshToken()).pipe( switchMap((res)=>{ this.isRefresh = false; this.tokenService.setAccessToken(res.accessToken); this.tokenService.setRefreshToken(res.refreshToken); return next.handle(this.setHeaders(request, res.accessToken)); }), catchError((refreshErr) => { this.isRefresh = false; this.tokenService.clearToken(); // 此处可添加跳转登录页的逻辑 return throwError(()=> refreshErr); }) )
3. 并发请求未等待刷新完成
页面刷新后若有多个并发请求,第一个请求触发401并开始刷新Token,后续请求会直接携带过期Token发送,导致多个401错误。
修复代码:
使用BehaviorSubject统一管理刷新状态,让所有等待的请求复用同一个刷新结果:
export class AuthInterceptor implements HttpInterceptor { private isRefresh = false; private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null); // 新增 constructor(private tokenService:TokenService, private authenticationService:AuthenticationService) {} // 省略intercept方法 private handleAuthorizationError(request:HttpRequest<any>, next:HttpHandler){ if(!this.isRefresh){ this.isRefresh = true; this.refreshTokenSubject.next(null); return this.authenticationService.refreshToken(this.tokenService.getRefreshToken()).pipe( switchMap((res)=>{ this.isRefresh = false; this.tokenService.setAccessToken(res.accessToken); this.tokenService.setRefreshToken(res.refreshToken); this.refreshTokenSubject.next(res.accessToken); return next.handle(this.setHeaders(request, res.accessToken)); }), catchError((refreshErr) => { this.isRefresh = false; this.tokenService.clearToken(); this.refreshTokenSubject.next(null); return throwError(()=> refreshErr); }) ) }else{ // 等待刷新完成后用新Token重新发送请求 return this.refreshTokenSubject.pipe( filter(token => token !== null), take(1), switchMap(token => { return next.handle(this.setHeaders(request, token)); }) ); } } }
内容的提问来源于stack exchange,提问作者Arkadian
相关产品推荐
相关产品推荐

