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,提问作者Андрій Степанов
相关产品推荐
相关产品推荐

