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

Angular14中JWT刷新后新令牌存入lazyUpdate而非Headers的问题

问题分析与解决方案

核心问题

你遇到的两个现象中,lazyUpdate是Angular HttpHeaders的正常内部实现,并非问题根源;真正导致请求未重发的原因是:拦截器的catchError逻辑中,调用handleRefToken后没有返回其生成的Observable,原请求直接抛出错误终止,重发逻辑并未被订阅执行。


1. 关于lazyUpdate的说明

Angular的HttpHeaders是不可变对象,调用set方法时不会直接修改原对象的headers属性,而是将更新操作存入lazyUpdate数组。当实际发送请求时,Angular会合并这些延迟更新,生成最终的请求头。所以你在控制台看到的lazyUpdate是正常现象,实际请求会正确携带Authorization头,无需纠结这个字段。


2. 修复请求重发逻辑

方案一:用RxJS操作符替代async/await(推荐,符合Angular响应式风格)

修改authService的refreshToken方法(返回Observable而非Promise)
public refreshToken(): Observable<Token> {
  const token = localStorage.getItem('refreshtoken');
  const header = new HttpHeaders().set(
    'Authorization',
    'Bearer ' + token!
  );
  return this.http.get<Token>(`${this.url}/authentication/refresh`, {
    headers: header,
  });
}
修改拦截器的handleRefToken方法
handleRefToken(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  const refreshtoken = localStorage.getItem('refreshtoken');

  if (!refreshtoken) {
    return throwError(() => 'BAC');
  }

  return this.authService.refreshToken().pipe(
    switchMap((newTokens) => {
      // 更新本地存储的令牌
      localStorage.setItem('accesstoken', newTokens.Tokens.accessToken);
      localStorage.setItem('refreshtoken', newTokens.Tokens.refreshToken);
      
      // 克隆请求并添加新令牌
      const authRequest = this.addTokenHeader(req, newTokens.Tokens.accessToken);
      // 重发请求
      return next.handle(authRequest);
    })
  );
}
修改拦截器的intercept方法中的catchError逻辑
intercept(
  req: HttpRequest<any>,
  next: HttpHandler
): Observable<HttpEvent<any>> {
  const accesstoken = this.authService.getAccessToken();
  const accessTokenExpired = this.authService.accessTokenExpired();
  let authRequest = req;

  if (accesstoken && !accessTokenExpired) {
    authRequest = this.addTokenHeader(req, accesstoken);
  }

  return next.handle(authRequest).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401) {
        // 返回重发请求的Observable,替换原错误流
        return this.handleRefToken(req, next);
      }
      return throwError(() => error);
    })
  );
}

方案二:保留async/await,需将Promise转换为Observable

如果坚持使用async/await,需修改拦截器的catchError逻辑,将handleRefToken返回的Promise转换为Observable:

修改handleRefToken方法
async handleRefToken(req: HttpRequest<any>, next: HttpHandler): Promise<HttpEvent<any>> {
  const refreshtoken = localStorage.getItem('refreshtoken');

  if (refreshtoken) {
    this.newTokens = await this.authService.refreshToken();
    
    localStorage.setItem('accesstoken', this.newTokens.Tokens.accessToken);
    localStorage.setItem('refreshtoken', this.newTokens.Tokens.refreshToken);

    const authRequest = this.addTokenHeader(req, this.newTokens.Tokens.accessToken);
    
    // 将Observable转换为Promise,适配async函数返回值
    return next.handle(authRequest).toPromise();
  }
  return Promise.reject('BAC');
}
修改拦截器的catchError逻辑
catchError((error: HttpErrorResponse) => {
  if (error.status === 401) {
    // 将Promise转换为Observable返回
    return from(this.handleRefToken(req, next));
  }
  return throwError(() => error);
})

验证修改效果

修复后,当401错误触发时:

  1. 拦截器会调用刷新令牌接口获取新令牌
  2. 更新本地存储的令牌
  3. 克隆原请求并携带新令牌重发
  4. 重发请求的响应会正常返回给业务代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 16:00:48