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

Angular中catchError为何无法捕获401错误?JWT刷新异常排查

JWT令牌刷新系统:401错误未被catchError捕获问题

正在实现标准JWT令牌刷新系统,已配置AuthGuard和AuthInterceptor(本人是初学者)。当前遇到的问题是:请求返回401错误时,catchError未按预期捕获该错误。


相关代码与配置:

前端AuthGuard代码

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { TokenService } from '../../services/authentication/token-service/token.service';
import { catchError, map, of } from 'rxjs';
import { HttpClient } from '@angular/common/http';

export const authGuard: CanActivateFn = (route, state) => {
  const tokenService: TokenService = inject(TokenService);
  const router: Router = inject(Router);
  const http: HttpClient = inject(HttpClient);

  const token = tokenService.getAccessToken();
    return http.get('/api/token/validate-access-token').pipe(
      map(() => true),
      catchError(() => {
        return of(false);
      })
    );
};

后端TokenController验证接口

[Authorize]
[HttpGet("validate-access-token")]
public async Task<ActionResult> ValidateAccessToken()
{
    return Ok();
}

该接口受[Authorize]保护,验证不通过时自动返回401,本应被Interceptor捕获并处理刷新逻辑。

前端AuthInterceptor代码

let isRefreshing = false;
let refreshSubject = new BehaviorSubject<boolean>(false);

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const service = inject(TokenService);
  const router = inject(Router);
  const excludedPaths = ['/.well-known/openid-configuration'];

  if (excludedPaths.some(path => req.url.includes(path)))
    return next(req);

  skipAuthCheck(req, next);

  const accessToken = service.getAccessToken();
  const clonedRequest = accessToken 
    ? req.clone({
        setHeaders: {
          Authorization: `Bearer ${accessToken}`
        }
      })
    : req;

  return next(clonedRequest).pipe(
    catchError(error => handleAuthError(error, service, router, clonedRequest, next))
  );
};

function skipAuthCheck (req: HttpRequest<any>, next: HttpHandlerFn) {
  if (req.context.get(SKIP_AUTH))
    return next(req);

  return null;
}

function handleAuthError(
  error: HttpErrorResponse,
  tokenService: TokenService,
  router: Router,
  req: HttpRequest<any>,
  next: HttpHandlerFn
): Observable<any> {
  if (error.status === 401) {
    if (!isRefreshing) {
      isRefreshing = true;
      refreshSubject.next(false);

      return tokenService.refreshToken().pipe(
        switchMap(() => {
          isRefreshing = false;
          refreshSubject.next(true);
          const accessToken = tokenService.getAccessToken();
          console.log('Access token after refresh: ', accessToken);
          if (!accessToken) {
            console.error('No access token after refresh');
          }
          const clonedRequest = req.clone({
            setHeaders: {
              Authorization: `Bearer ${accessToken}`
            },
            context: req.context.set(SKIP_AUTH, true)
          });
          return next(clonedRequest);
        }),
        catchError((err) => {
          isRefreshing = false;
          refreshSubject.next(false);
          tokenService.revokeToken();
          router.navigateByUrl('/login');
          console.error('Token refresh error: ', err);
          return throwError(() => new Error("Authentication Error: Redirected"));
        })
      );
      
    } else {
      return refreshSubject.pipe(
        filter(isRefreshed => isRefreshed),
        take(1),
        switchMap(() => {
          const clonedRequest = req.clone({
            setHeaders: {
              Authorization: `Bearer ${tokenService.getAccessToken()}`
            }
          });
          return next(clonedRequest);
        })
      );
    }
  }

  return throwError(() => error);
}

Interceptor逻辑:捕获401错误后尝试刷新令牌,刷新失败则跳转登录页。

前端TokenService刷新方法

public refreshToken(): Observable<string> {
    console.log('Attempting to refresh token...');
    return this._http.post<{accessToken: string }> (
      '/api/token/refresh', {}
    ).pipe(
      map(result => result.accessToken),
      tap((result) => {
        console.log('New access token:', result);
        this.setAccessToken(result);
      }),
      shareReplay(1),
      catchError((error) => {
        console.error('Refresh token error:', error);
        return throwError(() => error);
      })
    );
  }

后端TokenController刷新接口

[HttpPost("refresh")]
public async Task<ActionResult<object>> RefreshToken()
{
    if (Request.Cookies.TryGetValue("RefreshToken", out string? refreshToken))
    {
        var result = await _tokenManager.RefreshToken(refreshToken);

        if (result.Error == ErrorType.Unauthorized)
        {
            return Unauthorized();
        }

        var accessTokenModel = result.Data;
        return Ok(new { accessToken = accessTokenModel.AccessToken });
    }

    return Unauthorized();
}

HTTP客户端配置

provideHttpClient(
      withInterceptors([
        //toastInterceptor,
        //dateFormatInterceptor,
        authInterceptor,
        //camelCaseInterceptor
      ])
    ), 

JWT认证配置

services.Configure<JwtSettings>(
    configuration.GetSection("Jwt")
);

services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = configuration["Jwt:Issuer"],
        ValidAudience = configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]!))
    };
});

return services;
// ...
app.UseAuthentication();
app.UseAuthorization();

全局授权配置

builder.Services
    .AddControllers(
        options =>
        {
            options.Filters.Add(new AuthorizeFilter());
        }
    )

异常现象:

请求返回401错误,但仅在tokenService中打印了“Attempting to refresh token...”,后续的pipe处理、catchError逻辑均未执行,也未触发登录页跳转,请求甚至未进入后端控制器。

内容的提问来源于stack exchange,提问作者Андрій Степанов

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 04:05:04