Angular HttpClient在catchError中无法获取正确状态码问题
问题分析与解决方案
你的核心问题是:Token过期后端返回401,但前端拦截器拿到的error.status始终为0,导致无法触发刷新Token逻辑。这确实和CORS配置冲突有关,具体原因及解决方法如下:
1. 核心原因:Spring Security的CORS配置冲突
你在SecurityConfig中同时做了两件矛盾的事:
- 调用
http.cors().disable()禁用了Spring Security的CORS支持 - 又定义了
corsConfigurationSourceBean来配置跨域规则
这种情况下,自定义的CORS配置不会生效。当Token过期后端返回401时,响应没有携带正确的CORS头,浏览器出于安全策略拦截了该响应,导致前端只能拿到status=0的错误(这是浏览器跨域拦截的典型表现)。
2. 解决方案步骤
步骤1:修复Spring Security的CORS配置
移除http.cors().disable(),让Spring Security自动使用你定义的corsConfigurationSource Bean:
修改后的SecurityConfig配置代码:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity( securedEnabled = true, jsr250Enabled = true, prePostEnabled = true ) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors() // 保留cors(),启用自定义CORS配置 .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/auth/**").permitAll() .anyRequest().authenticated(); // Add our custom JWT security filter http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); http.exceptionHandling().authenticationEntryPoint(new AlwaysSendUnauthorized401AuthenticationEntryPoint()); } @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200", "http://localhost:8100")); configuration.setAllowedMethods(Arrays.asList("HEAD", "GET","POST", "PUT", "DELETE", "PATCH", "OPTIONS")); // 允许携带认证信息(比如Cookie、Token) configuration.setAllowCredentials(true); // 允许前端获取响应头 configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
步骤2:确保401错误响应携带CORS头
检查你的AlwaysSendUnauthorized401AuthenticationEntryPoint实现,确保它在返回401时也设置了CORS头,避免浏览器拦截错误响应:
public class AlwaysSendUnauthorized401AuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { // 从请求头获取Origin,动态设置允许的跨域源(更灵活) String origin = request.getHeader("Origin"); if (origin != null) { response.setHeader("Access-Control-Allow-Origin", origin); } response.setHeader("Access-Control-Allow-Credentials", "true"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token已过期或无效"); } }
步骤3:前端拦截器的小优化
可以在拦截器中增加对error.status === 0的判断,结合错误特征处理跨域导致的401拦截场景(可选,但更健壮):
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<Object>> { let authReq = req; const token = this.tokenService.getToken(); if (token != null) { authReq = this.addTokenHeader(req, token); } return next.handle(authReq).pipe(catchError(error => { console.log(error); // 增加对status=0的判断,结合是否是认证相关请求 const isAuthError = (error instanceof HttpErrorResponse && !authReq.url.includes('auth/login') && (error.status === 401 || (error.status === 0 && error.message.includes('CORS')))); if (isAuthError) { return this.handle401Error(authReq, next); } else{ console.log(error, authReq, error.status); } return throwError(error); })); }
3. 验证逻辑
修改完成后,当Token过期时:
- 后端会正确返回带有CORS头的401响应
- 浏览器不再拦截该响应,前端拦截器能拿到真实的
error.status=401 - 自动触发
handle401Error逻辑刷新Token,继续原请求
内容的提问来源于stack exchange,提问作者Sol
相关产品推荐
相关产品推荐

