Angular 5.2 & RxJS 5.5:HttpInterceptor拦截401重发请求遇阻
我来帮你搞定这个问题!在Angular的HTTP拦截器里处理401刷新令牌并重发请求,核心是要正确串联异步操作,确保刷新令牌的请求完成后,能带着新的请求头重新发起原请求。我给你一套可行的实现方案,再拆解里面的关键点:
完整拦截器实现代码
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse } from '@angular/common/http'; import { Observable, throwError, BehaviorSubject } from 'rxjs'; import { catchError, switchMap, take, filter } from 'rxjs/operators'; import { AuthService } from './auth.service'; // 假设你有处理令牌的AuthService @Injectable() export class AuthInterceptor implements HttpInterceptor { // 标记是否正在刷新令牌,避免并发请求重复触发刷新 private isRefreshing = false; // 用于通知等待中的请求:刷新已完成,可用新令牌重发 private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null); constructor(private authService: AuthService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> { // 先给请求带上当前的access token request = this.addAuthHeader(request); return next.handle(request).pipe( catchError((error) => { if (error instanceof HttpErrorResponse && error.status === 401) { return this.handle401Error(request, next); } else { // 非401错误直接抛出 return throwError(() => error); } }) ); } // 克隆请求并添加Authorization头 private addAuthHeader(request: HttpRequest<any>): HttpRequest<any> { const accessToken = this.authService.getAccessToken(); if (accessToken) { return request.clone({ setHeaders: { Authorization: `Bearer ${accessToken}` } }); } return request; } private handle401Error(request: HttpRequest<any>, next: HttpHandler): Observable<any> { if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); // 先发送刷新令牌请求,拿到新token后重发原请求 return this.authService.refreshToken().pipe( switchMap((newToken: any) => { this.isRefreshing = false; this.refreshTokenSubject.next(newToken.accessToken); // 用新token克隆原请求,发起重发 return next.handle(this.addAuthHeader(request)); }), catchError((refreshError) => { this.isRefreshing = false; // 刷新失败,直接退出登录 this.authService.logout(); return throwError(() => refreshError); }) ); } else { // 如果已有刷新请求在进行,等待刷新完成后用新token重发 return this.refreshTokenSubject.pipe( filter(token => token !== null), take(1), switchMap(token => { return next.handle(this.addAuthHeader(request)); }) ); } } }
关键问题解析
为什么之前catchError返回next.handle(newReq)没触发?
因为刷新令牌的请求是异步操作,你需要用switchMap把「刷新令牌」和「重发原请求」两个Observable串联起来。如果直接在catchError里返回next.handle(...),会导致新token还没拿到就发起请求,甚至因为刷新请求还在pending,导致重发逻辑没有正确订阅执行。
核心细节说明
- 不可变的
HttpRequest:Angular的请求对象是不可变的,必须用clone()方法修改请求头,不能直接修改原对象,否则不会生效。 - 并发请求处理:用
isRefreshing标记和BehaviorSubject避免多个请求同时触发401时,重复调用刷新令牌接口,所有等待的请求会在刷新完成后统一用新token重发。 - 刷新失败兜底:如果刷新令牌也失败(比如refresh token过期),直接执行退出登录逻辑,避免无限循环请求。
配套的AuthService示例
确保你的AuthService里有对应的令牌操作方法:
// auth.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthService { private accessTokenKey = 'access_token'; private refreshTokenKey = 'refresh_token'; constructor(private http: HttpClient) {} getAccessToken(): string | null { return localStorage.getItem(this.accessTokenKey); } getRefreshToken(): string | null { return localStorage.getItem(this.refreshTokenKey); } refreshToken(): Observable<any> { const refreshToken = this.getRefreshToken(); return this.http.post('/api/auth/refresh-token', { refreshToken }); } logout(): void { localStorage.removeItem(this.accessTokenKey); localStorage.removeItem(this.refreshTokenKey); // 这里可以添加跳转到登录页的逻辑 } }
最后别忘了注册拦截器
在app.module.ts的providers数组中注册拦截器:
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthInterceptor } from './auth.interceptor'; @NgModule({ // ...其他配置 providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ] }) export class AppModule { }
内容的提问来源于stack exchange,提问作者Joseph Montanez
相关产品推荐
相关产品推荐

