Angular拦截器令牌使用及自动刷新功能实现问题求助
Angular令牌刷新拦截器修复方案
原代码核心问题
- 在
switchMap中嵌套subscribe,破坏RxJS流结构,导致拦截器无法正确返回Observable,后续逻辑失效 - 刷新时机错误:请求完成后才判断令牌是否过期,应在发起请求前检查令牌是否即将过期并提前刷新
- 未处理并发请求:多请求同时触发时会重复调用刷新接口
- 令牌刷新后未正确复用原请求,逻辑不符合拦截器重试机制
修复后的拦截器代码
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable, BehaviorSubject, of } from 'rxjs'; import { SapToken } from './sapToken.service'; import { switchMap, filter, take, catchError } from 'rxjs/operators'; import { ISapToken } from './model'; @Injectable() export class SapTokenInterceptor implements HttpInterceptor { // 标记是否正在刷新令牌,避免并发请求重复触发刷新 private isRefreshing = false; private refreshTokenSubject: BehaviorSubject<ISapToken | null> = new BehaviorSubject<ISapToken | null>(null); constructor(private _sapToken: SapToken) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 跳过刷新令牌接口本身,防止循环拦截 if (req.url.includes('/refresh-token')) { return next.handle(req); } let authReq = req; const currentToken = this._sapToken.sapToken.value; // 检查令牌是否存在,且距离过期不足30分钟时触发刷新 if (currentToken) { const timeUntilExpire = this._sapToken.expireDuration(currentToken.expires); if (timeUntilExpire <= 30 * 60 * 1000) { return this.refreshTokenAndRetry(req, next); } // 令牌有效,直接添加请求头 authReq = this.addTokenToRequest(req, currentToken.accessToken); } return next.handle(authReq); } private refreshTokenAndRetry(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); // 调用刷新令牌接口 return this._sapToken.refreshSapSessionToken().pipe( switchMap((refreshedToken: ISapToken) => { this.isRefreshing = false; this.refreshTokenSubject.next(refreshedToken); this._sapToken.sapToken.next(refreshedToken); // 用新令牌重试原请求 return next.handle(this.addTokenToRequest(req, refreshedToken.accessToken)); }), catchError((err) => { this.isRefreshing = false; // 刷新失败可在此添加登出等降级逻辑 return next.handle(req); }) ); } else { // 已有刷新请求在处理,等待刷新完成后用新令牌重试 return this.refreshTokenSubject.pipe( filter(token => token !== null), take(1), switchMap(token => { return next.handle(this.addTokenToRequest(req, token!.accessToken)); }) ); } } // 封装添加令牌到请求头的通用方法 private addTokenToRequest(req: HttpRequest<any>, token: string): HttpRequest<any> { return req.clone({ headers: req.headers.set('X-SAPAccessToken', token) }); } }
关键修复说明
- 消除嵌套订阅:用RxJS操作符处理异步流,保证Observable的完整性
- 提前触发刷新:发起请求前检查令牌有效期,提前30分钟刷新,避免请求时令牌已过期
- 并发请求控制:通过
isRefreshing标记和refreshTokenSubject,确保同一时间仅一个刷新请求,其他请求等待复用新令牌 - 避免循环拦截:跳过刷新令牌接口的拦截逻辑
- 复用原请求:刷新完成后用新令牌重新发起原请求,符合拦截器的重试逻辑
内容的提问来源于stack exchange,提问作者Johansrk
相关产品推荐
相关产品推荐

