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

Angular无缝刷新令牌流程改造咨询:当前仅配置AccessToken

在Angular的HttpInterceptor中集成Refresh Token自动刷新逻辑

看来你已经搭好了基础的HttpInterceptor来注入access token,现在要加上长生命周期refresh token的自动刷新逻辑对吧?这在Angular里是很常见的场景,我来帮你完善这个拦截器的实现,兼顾请求重试、避免重复刷新和边界情况处理:

第一步:先确保你的鉴权服务(AuthService)具备核心方法

首先你需要一个AuthService来管理token的存储和刷新操作,至少要包含这些方法:

@Injectable({ providedIn: 'root' })
export class AuthService {
  // 假设你用localStorage或其他方式存储token
  private readonly ACCESS_TOKEN_KEY = 'access_token';
  private readonly REFRESH_TOKEN_KEY = 'refresh_token';

  getAccessToken(): string | null {
    return localStorage.getItem(this.ACCESS_TOKEN_KEY);
  }

  getRefreshToken(): string | null {
    return localStorage.getItem(this.REFRESH_TOKEN_KEY);
  }

  // 调用后端刷新token的接口,返回新的access和refresh token
  refreshAccessToken(): Observable<{ accessToken: string; refreshToken: string }> {
    const refreshToken = this.getRefreshToken();
    return this.http.post<{ accessToken: string; refreshToken: string }>(
      '/api/auth/refresh',
      { refreshToken }
    ).pipe(
      tap((tokens) => {
        // 存储新的token
        localStorage.setItem(this.ACCESS_TOKEN_KEY, tokens.accessToken);
        localStorage.setItem(this.REFRESH_TOKEN_KEY, tokens.refreshToken);
      })
    );
  }

  logout(): void {
    localStorage.removeItem(this.ACCESS_TOKEN_KEY);
    localStorage.removeItem(this.REFRESH_TOKEN_KEY);
    // 这里可以添加跳转到登录页的逻辑
  }
}

第二步:修改你的HttpInterceptor,添加刷新逻辑

接下来更新你的拦截器,加入错误拦截和token刷新重试的逻辑,重点处理401 Unauthorized响应:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  // 用于标记是否正在刷新token,避免并发请求重复刷新
  private refreshTokenInProgress = false;
  // 用于通知其他等待中的请求token已刷新完成
  private tokenRefreshSubject = new Subject<void>();

  constructor(private authService: AuthService, private router: Router) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 1. 注入access token到请求头
    let requestToHandle = req;
    const accessToken = this.authService.getAccessToken();
    if (accessToken) {
      requestToHandle = this.addTokenToRequest(req, accessToken);
    }

    // 2. 处理请求和响应错误
    return next.handle(requestToHandle).pipe(
      catchError((error: HttpErrorResponse) => {
        // 只处理401 Unauthorized错误,且不是刷新token的请求本身(避免循环)
        if (error.status === 401 && !req.url.includes('/api/auth/refresh')) {
          return this.handle401Error(requestToHandle, next);
        }
        // 其他错误直接抛出
        return throwError(() => error);
      })
    );
  }

  private addTokenToRequest(req: HttpRequest<any>, token: string): HttpRequest<any> {
    return req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
  }

  private handle401Error(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 如果没有正在刷新token,就发起刷新请求
    if (!this.refreshTokenInProgress) {
      this.refreshTokenInProgress = true;
      this.tokenRefreshSubject.next();

      return this.authService.refreshAccessToken().pipe(
        switchMap(() => {
          // 刷新成功后,标记刷新完成,通知所有等待的请求
          this.refreshTokenInProgress = false;
          this.tokenRefreshSubject.complete();
          // 用新的access token重试原请求
          const newAccessToken = this.authService.getAccessToken();
          return next.handle(this.addTokenToRequest(req, newAccessToken!));
        }),
        catchError((err) => {
          // 刷新失败,说明refresh token也失效了,执行登出
          this.refreshTokenInProgress = false;
          this.tokenRefreshSubject.complete();
          this.authService.logout();
          return throwError(() => err);
        })
      );
    } else {
      // 如果已经在刷新token,等待刷新完成后重试请求
      return this.tokenRefreshSubject.pipe(
        filter(() => !this.refreshTokenInProgress),
        take(1),
        switchMap(() => {
          const newAccessToken = this.authService.getAccessToken();
          return next.handle(this.addTokenToRequest(req, newAccessToken!));
        })
      );
    }
  }
}

关键逻辑说明

  • 避免重复刷新:用refreshTokenInProgress标志和tokenRefreshSubject来控制,当多个请求同时触发401时,只会发起一次refresh请求,其他请求等待刷新完成后重试。
  • 防止循环请求:判断请求URL是否是刷新token的接口,避免401时再次调用刷新接口导致无限循环。
  • 边界处理:如果refresh token也失效(刷新接口返回错误),直接执行登出操作,引导用户重新登录。
  • 请求重试:刷新token成功后,用新的access token克隆原请求并重新发起,保证用户无感知。

最后别忘了在app.module.ts里注册这个拦截器:

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ]
})
export class AppModule {}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:26:38