JWT认证拦截器中getToken方法未等待Promise完成致请求失败
这问题我太熟了!核心痛点就是你的拦截器没等getToken()这个异步操作完成,就急着发请求了——毕竟Promise是异步的,同步拿值肯定抓不到更新后的令牌。下面给你一套完整的解决方案,从修正TokenService到调整拦截器逻辑一步到位:
第一步:统一
getToken()的异步返回类型 首先要把getToken()改成始终返回Promise
export class TokenService { private refresh = false; private refreshPromise?: Promise<string>; // 用于等待正在进行的刷新请求 constructor(public injector: Injector) { } public async getToken(): Promise<string> { // 1. 先检查本地令牌是否有效 const currentToken = localStorage.getItem('access_token'); if (currentToken && this.isTokenValid(currentToken)) { return currentToken; } // 2. 如果正在刷新,等待刷新完成再返回新令牌 if (this.refresh) { await this.refreshPromise; return localStorage.getItem('access_token') || ''; } try { this.refresh = true; // 3. 调用刷新令牌的服务(这里假设你有AuthService处理刷新) const authService = this.injector.get(AuthService); const newTokens = await authService.refreshTokens(); // 4. 存储新令牌 localStorage.setItem('access_token', newTokens.accessToken); localStorage.setItem('refresh_token', newTokens.refreshToken); return newTokens.accessToken; } catch (error) { // 刷新失败的处理:清空令牌、跳登录页等 console.error('令牌刷新失败:', error); localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); throw error; // 让拦截器捕获错误做后续处理 } finally { this.refresh = false; this.refreshPromise = undefined; } } // 辅助方法:检查令牌是否过期 private isTokenValid(token: string): boolean { try { const payload = JSON.parse(atob(token.split('.')[1])); const expirationTime = payload.exp * 1000; return Date.now() < expirationTime; } catch (error) { return false; // 解析失败直接判定为无效 } } }
第二步:调整拦截器,等待令牌获取完成再发请求
拦截器里不能同步调用getToken(),要把Promise转成Observable,用switchMap等待异步操作完成后,再给请求添加令牌头:
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable, from } from 'rxjs'; import { switchMap, catchError } from 'rxjs/operators'; import { TokenService } from './token.service'; import { Router } from '@angular/router'; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor( private tokenService: TokenService, private router: Router ) { } intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { // 把Promise转成Observable,等待令牌获取完成 return from(this.tokenService.getToken()).pipe( switchMap(token => { // 如果拿到有效令牌,克隆请求并添加Authorization头 if (token) { const authRequest = request.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); return next.handle(authRequest); } // 没有令牌,直接发送原请求(或根据业务跳登录) return next.handle(request); }), catchError(error => { // 捕获令牌刷新失败的错误,跳转到登录页 this.router.navigate(['/login']); throw error; }) ); } }
关键细节说明
- 防重复刷新:通过
refresh状态和refreshPromise,避免多个请求同时触发令牌刷新接口,减少无效请求。 - 统一异步返回:
getToken()用async/await封装所有逻辑,确保拦截器能稳定等待令牌就绪。 - 错误处理:刷新失败时清空本地令牌并跳转登录,避免死循环。
内容的提问来源于stack exchange,提问作者SHIVAM GUPTA
相关产品推荐
相关产品推荐

