Angular无缝刷新令牌流程改造咨询:当前仅配置AccessToken
在Angular的HttpInterceptor中集成Refresh Token自动刷新逻辑
看来你已经搭好了基础的HttpInterceptor来注入access token,现在要加上长生命周期refresh token的自动刷新逻辑对吧?这在Angular里是很常见的场景,我来帮你完善这个拦截器的实现,兼顾请求重试、避免重复刷新和边界情况处理:
第一步:先确保你的鉴权服务(AuthService)具备核心方法
首先你需要一个AuthService来管理token的存储和刷新操作,至少要包含这些方法:
@Injectable({ providedIn: 'root' }) export class AuthService { // 假设你用localStorage或其他方式存储token private readonly ACCESS_TOKEN_KEY = 'access_token'; private readonly REFRESH_TOKEN_KEY = 'refresh_token'; getAccessToken(): string | null { return localStorage.getItem(this.ACCESS_TOKEN_KEY); } getRefreshToken(): string | null { return localStorage.getItem(this.REFRESH_TOKEN_KEY); } // 调用后端刷新token的接口,返回新的access和refresh token refreshAccessToken(): Observable<{ accessToken: string; refreshToken: string }> { const refreshToken = this.getRefreshToken(); return this.http.post<{ accessToken: string; refreshToken: string }>( '/api/auth/refresh', { refreshToken } ).pipe( tap((tokens) => { // 存储新的token localStorage.setItem(this.ACCESS_TOKEN_KEY, tokens.accessToken); localStorage.setItem(this.REFRESH_TOKEN_KEY, tokens.refreshToken); }) ); } logout(): void { localStorage.removeItem(this.ACCESS_TOKEN_KEY); localStorage.removeItem(this.REFRESH_TOKEN_KEY); // 这里可以添加跳转到登录页的逻辑 } }
第二步:修改你的HttpInterceptor,添加刷新逻辑
接下来更新你的拦截器,加入错误拦截和token刷新重试的逻辑,重点处理401 Unauthorized响应:
@Injectable() export class AuthInterceptor implements HttpInterceptor { // 用于标记是否正在刷新token,避免并发请求重复刷新 private refreshTokenInProgress = false; // 用于通知其他等待中的请求token已刷新完成 private tokenRefreshSubject = new Subject<void>(); constructor(private authService: AuthService, private router: Router) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 1. 注入access token到请求头 let requestToHandle = req; const accessToken = this.authService.getAccessToken(); if (accessToken) { requestToHandle = this.addTokenToRequest(req, accessToken); } // 2. 处理请求和响应错误 return next.handle(requestToHandle).pipe( catchError((error: HttpErrorResponse) => { // 只处理401 Unauthorized错误,且不是刷新token的请求本身(避免循环) if (error.status === 401 && !req.url.includes('/api/auth/refresh')) { return this.handle401Error(requestToHandle, next); } // 其他错误直接抛出 return throwError(() => error); }) ); } private addTokenToRequest(req: HttpRequest<any>, token: string): HttpRequest<any> { return req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); } private handle401Error(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 如果没有正在刷新token,就发起刷新请求 if (!this.refreshTokenInProgress) { this.refreshTokenInProgress = true; this.tokenRefreshSubject.next(); return this.authService.refreshAccessToken().pipe( switchMap(() => { // 刷新成功后,标记刷新完成,通知所有等待的请求 this.refreshTokenInProgress = false; this.tokenRefreshSubject.complete(); // 用新的access token重试原请求 const newAccessToken = this.authService.getAccessToken(); return next.handle(this.addTokenToRequest(req, newAccessToken!)); }), catchError((err) => { // 刷新失败,说明refresh token也失效了,执行登出 this.refreshTokenInProgress = false; this.tokenRefreshSubject.complete(); this.authService.logout(); return throwError(() => err); }) ); } else { // 如果已经在刷新token,等待刷新完成后重试请求 return this.tokenRefreshSubject.pipe( filter(() => !this.refreshTokenInProgress), take(1), switchMap(() => { const newAccessToken = this.authService.getAccessToken(); return next.handle(this.addTokenToRequest(req, newAccessToken!)); }) ); } } }
关键逻辑说明
- 避免重复刷新:用
refreshTokenInProgress标志和tokenRefreshSubject来控制,当多个请求同时触发401时,只会发起一次refresh请求,其他请求等待刷新完成后重试。 - 防止循环请求:判断请求URL是否是刷新token的接口,避免401时再次调用刷新接口导致无限循环。
- 边界处理:如果refresh token也失效(刷新接口返回错误),直接执行登出操作,引导用户重新登录。
- 请求重试:刷新token成功后,用新的access token克隆原请求并重新发起,保证用户无感知。
最后别忘了在app.module.ts里注册这个拦截器:
@NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ] }) export class AppModule {}
内容的提问来源于stack exchange,提问作者zaitsman
相关产品推荐
相关产品推荐

