Angular刷新token时误带过期access_token至请求头的问题排查
问题:Angular刷新Token时携带过期Access Token导致后端报错
我正在开发PHP后端+Angular前端的应用,登录模块功能正常,但实现refresh_token时遇到问题:
- 配置了两个HttpInterceptor:JWTInterceptor负责给所有请求加access_token(功能正常),tokenInterceptor捕获过期JWT并刷新Token
- 调用refresh token接口时,请求头会被带入过期的access_token,后端返回"Expired JWT Token"错误
- Postman测试确认:不带access_token调用刷新接口正常,携带过期token则触发错误
原JWTInterceptor代码
import { HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest, } from '@angular/common/http'; import {catchError, Observable, switchMap, throwError} from "rxjs"; import {inject} from "@angular/core"; import {AuthService} from "./services/auth.service"; import {Router} from "@angular/router"; export const JwtInterceptor: HttpInterceptorFn = (req, next) => { let authToken = localStorage.getItem('access_token'); console.log("JwtInterceptor") if (authToken) { let authReq = req.clone({ setHeaders: { Authorization: `Bearer ${authToken}`, }, }); return next(authReq); } else { return next(req); } }
原tokenInterceptor代码
import {HttpErrorResponse, HttpInterceptorFn} from '@angular/common/http'; import {catchError, switchMap, throwError} from "rxjs"; import {inject} from "@angular/core"; import {AuthService} from "./services/auth.service"; export const tokenInterceptor: HttpInterceptorFn = (req, next) => { console.log("TokenInterceptor") let authToken = localStorage.getItem('token'); let authService = inject(AuthService); if (authToken) { req = req.clone({ setHeaders: { Authorization: `Bearer ${authToken}`, }, }); } return next(req).pipe( catchError((errorData: HttpErrorResponse) => { if(errorData.status == 401) { console.error("Token Interceptor Error to 401"); return authService.refreshToken().pipe( switchMap(() => { console.error("Refresh token done") const newAccessToken = localStorage.getItem('access_token'); // Retry the original request with the new access token req = req.clone({ setHeaders: { Authorization: `Bearer ${newAccessToken}`, }, }); return next(req); }), catchError((error) => { // Handle refresh token error (e.g., redirect to login page) console.error('Error handling expired access token:', error); return throwError(error); }) ) } throw errorData; }) ); };
问题分析
- 拦截器执行冲突:两个拦截器都给请求添加Authorization头,且JwtInterceptor会给refresh_token接口也带上过期的access_token
- tokenInterceptor的错误:错误读取了
localStorage.getItem('token')(应该是access_token),同时未跳过refresh_token接口的头添加逻辑 - 刷新请求未特殊处理:调用refreshToken时没有剥离过期的Authorization头
修复方案
修复后的JwtInterceptor
import { HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest, } from '@angular/common/http'; export const JwtInterceptor: HttpInterceptorFn = (req, next) => { // 跳过刷新token的接口,避免添加过期的access_token if (req.url.includes('/refresh-token')) { return next(req); } const authToken = localStorage.getItem('access_token'); if (authToken) { const authReq = req.clone({ setHeaders: { Authorization: `Bearer ${authToken}`, }, }); return next(authReq); } return next(req); }
修复后的tokenInterceptor
import {HttpErrorResponse, HttpInterceptorFn} from '@angular/common/http'; import {catchError, switchMap, throwError} from "rxjs"; import {inject} from "@angular/core"; import {AuthService} from "./services/auth.service"; import {Router} from "@angular/router"; export const tokenInterceptor: HttpInterceptorFn = (req, next) => { const authService = inject(AuthService); const router = inject(Router); // 跳过刷新token的接口,避免重复添加头 if (req.url.includes('/refresh-token')) { return next(req); } const authToken = localStorage.getItem('access_token'); let authReq = req; if (authToken) { authReq = req.clone({ setHeaders: { Authorization: `Bearer ${authToken}`, }, }); } return next(authReq).pipe( catchError((errorData: HttpErrorResponse) => { // 只处理非刷新接口的401错误,避免循环调用 if (errorData.status === 401 && !req.url.includes('/refresh-token')) { return authService.refreshToken().pipe( switchMap(() => { const newAccessToken = localStorage.getItem('access_token'); // 用新token重试原始请求 const retryReq = req.clone({ setHeaders: { Authorization: `Bearer ${newAccessToken}`, }, }); return next(retryReq); }), catchError((error) => { // 刷新失败,清除本地token并跳转登录页 localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); router.navigate(['/login']); return throwError(error); }) ); } return throwError(errorData); }) ); };
关键修改说明
- 两个拦截器都添加了跳过refresh_token接口的逻辑,确保该请求不带过期的access_token
- 修正了tokenInterceptor中错误的token读取逻辑
- 新增了刷新失败后的用户引导(清除token+跳转登录)
- 增加了防循环判断,避免刷新接口的401错误触发无限刷新
内容的提问来源于stack exchange,提问作者PawelC
相关产品推荐
相关产品推荐

