Angular 17函数式拦截器实现令牌刷新:注入与逻辑整合问题求解
Angular 17 函数式拦截器实现令牌刷新问题
问题背景
之前使用类式拦截器实现了用户令牌刷新功能,代码如下:
@Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private inject: Injector) {} intercept(request: HttpRequest < any > , next: HttpHandler): Observable < HttpEvent < any >> { let tokenService = this.inject.get(TokenService); let authReq = request; authReq = this.AddTokenHeader(request, tokenService.getAccessToken()) return next.handle(authReq).pipe( catchError(errorData => { if (errorData.status == 401) { return this.handleRefreshToken(request, next); } return throwError(errorData); }) ); } AddTokenHeader(request: HttpRequest < any > , token: any) { return request.clone({ headers: request.headers.set('Authorization', 'Bearer ' + token) }); } handleRefreshToken(request: HttpRequest < any > , next: HttpHandler) { let tokenService = this.inject.get(TokenService); let authService = this.inject.get(AuthService); let sessionStorageService = this.inject.get(SessionStorageService); var refreshTokenVM = new RefreshTokenVM(); refreshTokenVM.UserId = tokenService.getUserId(); refreshTokenVM.RefreshToken! = tokenService.getRefreshToken() !; return tokenService.refreshToken(refreshTokenVM).pipe( switchMap((data: any) => { sessionStorageService.saveData('accessToken', data.accessToken); sessionStorageService.saveData('refreshToken', data.refreshToken); return next.handle(this.AddTokenHeader(request, data.accessToken)) }), catchError(errorData => { authService.logout(); return throwError(errorData); }) ); } }
升级到Angular 17后需要转换为函数式拦截器,目前的尝试代码:
export const authInterceptor: HttpInterceptorFn = (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => { const clonedRequest = request.clone({ setHeaders: { Authorization: 'Bearer ' + 'token', } }); return next(clonedRequest).pipe( catchError(errorData => { if (errorData.status == 401) { } return throwError(() => errorData); }) ); };
遇到的核心问题:
- 无法在拦截器函数中注入
tokenService(令牌存储在SessionStorage中) - 不清楚如何将
handleRefreshToken逻辑整合到函数式拦截器中
解决方案
1. 在函数式拦截器中注入服务
Angular 17的函数式拦截器可以直接通过inject()函数获取依赖实例,无需构造函数或Injector。只需在拦截器函数内部调用inject(服务类)即可:
import { inject } from '@angular/core'; // 导入你的服务 import { TokenService } from './path/to/token.service'; import { AuthService } from './path/to/auth.service'; import { SessionStorageService } from './path/to/session-storage.service'; export const authInterceptor: HttpInterceptorFn = (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => { // 直接注入所需服务 const tokenService = inject(TokenService); const authService = inject(AuthService); const sessionStorageService = inject(SessionStorageService); // ...后续逻辑 };
2. 整合令牌刷新逻辑
将原handleRefreshToken的逻辑内联到拦截器的catchError流程中,或封装为内部函数,利用已注入的服务完成刷新:
完整的函数式拦截器代码如下:
import { inject } from '@angular/core'; import { HttpInterceptorFn, HttpRequest, HttpHandlerFn, HttpEvent } from '@angular/common/http'; import { Observable, throwError, switchMap, catchError } from 'rxjs'; import { TokenService } from './path/to/token.service'; import { AuthService } from './path/to/auth.service'; import { SessionStorageService } from './path/to/session-storage.service'; import { RefreshTokenVM } from './path/to/refresh-token-vm'; export const authInterceptor: HttpInterceptorFn = (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => { const tokenService = inject(TokenService); const authService = inject(AuthService); const sessionStorageService = inject(SessionStorageService); // 封装添加Token头的工具函数 const addTokenHeader = (req: HttpRequest<unknown>, token: string): HttpRequest<unknown> => { return req.clone({ headers: req.headers.set('Authorization', `Bearer ${token}`) }); }; // 给初始请求添加Token const authReq = addTokenHeader(request, tokenService.getAccessToken()); return next(authReq).pipe( catchError(errorData => { // 处理401未授权场景 if (errorData.status === 401) { // 构建刷新Token请求体 const refreshTokenVM = new RefreshTokenVM(); refreshTokenVM.UserId = tokenService.getUserId(); refreshTokenVM.RefreshToken = tokenService.getRefreshToken()!; // 发起刷新Token请求 return tokenService.refreshToken(refreshTokenVM).pipe( switchMap((data: any) => { // 更新存储的令牌 sessionStorageService.saveData('accessToken', data.accessToken); sessionStorageService.saveData('refreshToken', data.refreshToken); // 用新Token重新发起原请求 return next(addTokenHeader(request, data.accessToken)); }), catchError(refreshError => { // 刷新失败,执行登出逻辑 authService.logout(); return throwError(() => refreshError); }) ); } // 其他错误直接抛出 return throwError(() => errorData); }) ); };
3. 额外优化建议
- 确保所有注入的服务都添加了
@Injectable({ providedIn: 'root' })装饰器,这样才能通过inject()正确获取实例。 - 避免并发刷新请求:如果多个请求同时返回401,会触发多次刷新接口调用。可以在
TokenService中添加一个状态Subject来控制刷新状态,示例如下:
// 在TokenService中添加 import { BehaviorSubject } from 'rxjs'; private isRefreshing$ = new BehaviorSubject<boolean>(false); // 在拦截器中判断 if (tokenService.isRefreshing$.value) { return tokenService.isRefreshing$.pipe( filter(isRefreshing => !isRefreshing), take(1), switchMap(() => next(addTokenHeader(request, tokenService.getAccessToken()))) ); } // 刷新开始时设置isRefreshing$.next(true),结束后设置为false
内容的提问来源于stack exchange,提问作者Alex Wright
相关产品推荐
相关产品推荐

