You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular技术问询:如何通过认证拦截器刷新JWT令牌并续期token_b

Angular认证拦截器:自动处理token_b过期刷新方案

嘿,我来帮你搞定这个token自动刷新的问题!你的场景我完全get:用户登录拿到token_a,所有API请求都要用token_a生成的token_b,拦截器已经能给请求加token_b,但token_b有效期短,得在它过期的时候自动重新生成,对吧?下面是完善后的拦截器实现,附带详细逻辑说明:

完整的auth.interceptor.ts代码

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpErrorResponse } from '@angular/common/http';
import { Observable, BehaviorSubject, throwError } from 'rxjs';
import { catchError, filter, take, switchMap } from 'rxjs/operators';
import { AuthService } from './auth.service'; // 假设你有专门的AuthService管理token

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  // 标记是否正在刷新token,避免并发请求重复刷新
  private isRefreshing = false;
  // 用于通知等待中的请求刷新完成
  private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

  constructor(private authService: AuthService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let authRequest = request;
    const currentTokenB = this.authService.getTokenB();
    
    if (currentTokenB) {
      // 先检查token_b是否过期
      if (!this.isTokenExpired(currentTokenB)) {
        // 没过期就直接把token_b加到请求头
        authRequest = this.attachTokenToRequest(request, currentTokenB);
      } else {
        // 过期了就走刷新流程
        return this.handleTokenRefresh(request, next);
      }
    }

    return next.handle(authRequest).pipe(
      catchError((error: HttpErrorResponse) => {
        // 如果请求返回401(未授权),说明token_b可能过期了(比如本地时间不准导致提前检查失效),再试一次刷新
        if (error.status === 401 && !request.url.includes('/refresh-token-b')) {
          return this.handleTokenRefresh(request, next);
        }
        return throwError(() => error);
      })
    );
  }

  // 把token_b附加到请求头的工具方法
  private attachTokenToRequest(request: HttpRequest<any>, token: string): HttpRequest<any> {
    return request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}` // 这里根据你的后端要求调整格式,比如有的是Token xxx
      }
    });
  }

  // 检查token_b是否过期的方法
  private isTokenExpired(token: string): boolean {
    // 解析JWT的payload部分,注意如果你的token不是JWT格式,要改成对应的解析逻辑
    const payload = JSON.parse(atob(token.split('.')[1]));
    // exp字段是秒级时间戳,转成毫秒和当前时间对比
    const expirationTime = payload.exp * 1000;
    return Date.now() > expirationTime;
  }

  // 处理token_b刷新的核心逻辑
  private handleTokenRefresh(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);

      // 调用AuthService里的方法,用token_a去后端请求新的token_b
      return this.authService.refreshTokenB().pipe(
        switchMap((newTokenB: string) => {
          this.isRefreshing = false;
          this.refreshTokenSubject.next(newTokenB);
          // 更新本地存储的token_b
          this.authService.setTokenB(newTokenB);
          // 用新的token_b重新发起原来的请求
          return next.handle(this.attachTokenToRequest(request, newTokenB));
        }),
        catchError((error) => {
          this.isRefreshing = false;
          // 如果刷新失败,一般是token_a也过期了,直接跳登录页
          this.authService.logout();
          return throwError(() => error);
        })
      );
    } else {
      // 如果已经在刷新token了,就让后续请求等着,拿到新token后再继续
      return this.refreshTokenSubject.pipe(
        filter(token => token !== null),
        take(1),
        switchMap(token => {
          return next.handle(this.attachTokenToRequest(request, token));
        })
      );
    }
  }
}

关键逻辑说明

  • 避免并发刷新:用isRefreshing标志和BehaviorSubject,防止多个请求同时触发token刷新,减少不必要的API调用。
  • 双重过期检查:提前在前端检查token是否过期,同时加上401返回后的兜底刷新,避免因为本地时间偏差导致的判断失误。
  • 依赖AuthService:你需要自己实现AuthService里的几个核心方法:
    • getTokenB():从本地存储(比如localStorage/sessionStorage)获取当前token_b
    • setTokenB():把新的token_b存到本地
    • refreshTokenB():调用后端API,用token_a换取新的token_b
    • logout():清除本地token,跳转到登录页

注册拦截器

最后别忘了在app.module.ts里把拦截器注册到Angular的HTTP拦截器链中:

import { NgModule } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';

@NgModule({
  imports: [HttpClientModule],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true // 允许多个拦截器
    }
  ]
})
export class AppModule {}

内容的提问来源于stack exchange,提问作者Kay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:48:39