Angular拦截器捕获Token过期,实现自动刷新并重发请求问询
我来帮你搞定这个token过期自动刷新并重发请求的需求,这是前后端分离项目里超常见的场景,咱们一步步来调整你的拦截器代码:
调整后的Angular拦截器实现
我们需要在拦截器里完成「捕获token过期错误→刷新token→重发原请求」的完整逻辑,同时还要避免多个请求同时触发重复刷新的问题,下面是完整的实现代码:
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from '@angular/common/http'; import { Observable, BehaviorSubject, throwError } from 'rxjs'; import { catchError, filter, take, switchMap } from 'rxjs/operators'; import { AuthService } from './auth.service'; // 假设你有专门处理token的AuthService @Injectable() export class AuthInterceptor implements HttpInterceptor { // 标记是否正在刷新token,避免并发请求重复触发刷新 private isRefreshing = false; // 用BehaviorSubject存储等待重发的请求队列 private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null); constructor(private authService: AuthService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 先给需要授权的请求添加Authorization头 const token = this.authService.getToken(); if (token) { request = this.addTokenToRequest(request, token); } return next.handle(request).pipe( catchError((error: HttpErrorResponse) => { // 判断是否是Laravel返回的token过期错误 if (error.status === 401 && error.error?.message === 'token_expired') { return this.handleTokenRefresh(request, next); } // 其他错误直接抛出,交给业务组件处理 return throwError(() => error); }) ); } // 给请求添加Authorization头的工具方法 private addTokenToRequest(request: HttpRequest<any>, token: string): HttpRequest<any> { return request.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); } // 处理token刷新和请求重发的核心逻辑 private handleTokenRefresh(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); // 调用AuthService的刷新token方法 return this.authService.refreshToken().pipe( switchMap((newToken: string) => { this.isRefreshing = false; this.refreshTokenSubject.next(newToken); // 更新本地存储的token this.authService.saveToken(newToken); // 用新token重新发送原来的请求 return next.handle(this.addTokenToRequest(request, newToken)); }), catchError((err) => { this.isRefreshing = false; // 如果刷新token失败(比如refresh token也过期了),直接登出跳登录页 this.authService.logout(); return throwError(() => err); }) ); } else { // 如果已经在刷新token,就等待刷新完成后再重发当前请求 return this.refreshTokenSubject.pipe( filter(token => token !== null), take(1), switchMap(token => { return next.handle(this.addTokenToRequest(request, token)); }) ); } } }
配套的AuthService示例
你需要一个专门的AuthService来处理token的存储、获取和刷新请求,示例代码如下:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, map } from 'rxjs'; import { Router } from '@angular/router'; @Injectable({ providedIn: 'root' }) export class AuthService { private readonly tokenKey = 'auth_token'; private readonly refreshTokenKey = 'refresh_token'; constructor(private http: HttpClient, private router: Router) {} // 从本地存储获取当前token getToken(): string | null { return localStorage.getItem(this.tokenKey); } // 保存新token到本地存储 saveToken(token: string): void { localStorage.setItem(this.tokenKey, token); } // 获取refresh token getRefreshToken(): string | null { return localStorage.getItem(this.refreshTokenKey); } // 调用Laravel的刷新token接口 refreshToken(): Observable<string> { const refreshToken = this.getRefreshToken(); return this.http.post<any>('/api/refresh-token', { refresh_token: refreshToken }) .pipe( map(response => { // 假设Laravel返回包含新access_token和refresh_token的响应 this.saveToken(response.access_token); localStorage.setItem(this.refreshTokenKey, response.refresh_token); return response.access_token; }) ); } // 登出操作:清理token并跳转登录页 logout(): void { localStorage.removeItem(this.tokenKey); localStorage.removeItem(this.refreshTokenKey); this.router.navigate(['/login']); } }
关键细节提醒
- 排除无需授权的请求:如果登录、刷新token这类请求不需要加Authorization头,记得在拦截器开头排除,避免无限循环:
if (request.url.includes('/api/login') || request.url.includes('/api/refresh-token')) { return next.handle(request); } - 适配后端错误格式:根据你Laravel实际返回的错误结构调整判断逻辑,比如有些后端会把错误信息放在
error.error.message里,需要对应修改。 - 并发请求处理:代码里的
isRefreshing和refreshTokenSubject已经处理了并发场景,多个请求同时因token过期失败时,只会触发一次刷新,所有请求都会用新token重发。
内容的提问来源于stack exchange,提问作者Abhishek
相关产品推荐
相关产品推荐

