使用JWT保护Web应用时遇CORS错误及403响应问题求助
解决Angular + Spring Boot JWT认证中的CORS 403问题
我之前在做Angular+Spring Boot JWT认证的时候也踩过一模一样的坑——明明配置了CORS还是报403,后来发现是几个细节没处理到位,给你梳理一下排查方向:
1. 检查Spring Boot的CORS配置是否覆盖关键参数
很多时候CORS配置看起来没问题,但漏掉了允许携带凭证或者用了过时的配置项。比如Spring Boot 2.4+推荐用allowedOriginPatterns而非allowedOrigins,后者对带端口的域名支持有限。
给你一个完整的CORS配置示例,结合Spring Security一起用:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and() // 先启用CORS配置 .csrf().disable() // JWT场景下通常关闭CSRF .authorizeRequests() // 必须放行OPTIONS预检请求,因为它不带JWT令牌 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 放行登录接口(获取JWT的入口) .antMatchers("/auth/login").permitAll() // 其他接口都需要认证 .anyRequest().authenticated() // 添加JWT过滤器 .and() .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } // 单独配置CORS的Bean @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); // 允许你的Angular应用域名,支持通配符或者具体域名 config.setAllowedOriginPatterns(Arrays.asList("http://localhost:4200")); // 允许所有请求头,包括JWT所在的Authorization头 config.setAllowedHeaders(Arrays.asList("*")); // 允许所有HTTP方法,明确包含OPTIONS config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 关键:允许携带凭证(JWT在请求头里,必须开这个) config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // 对所有接口生效 source.registerCorsConfiguration("/**", config); return source; } // 这里替换成你的JWT过滤器实例 @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } }
如果你的Spring Boot版本是3.x(用Spring Security 6+),配置方式略有不同,改用SecurityFilterChain:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers("/auth/login").permitAll() .anyRequest().authenticated()) .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } // 同样的corsConfigurationSource Bean... }
2. 确保JWT过滤器跳过OPTIONS预检请求
预检请求(OPTIONS)是浏览器自动发送的,不会携带JWT令牌。如果你的JWT过滤器直接拦截了所有请求并验证令牌,就会返回403。所以必须在过滤器里跳过OPTIONS请求:
public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 跳过OPTIONS方法的预检请求 if (HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); filterChain.doFilter(request, response); return; } // 你的JWT令牌提取和验证逻辑 String token = extractTokenFromRequest(request); if (token != null && jwtTokenUtil.validateToken(token)) { // 将认证信息存入SecurityContext Authentication auth = jwtTokenUtil.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(auth); } filterChain.doFilter(request, response); } // 提取请求头中的JWT令牌 private String extractTokenFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (bearerToken != null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }
3. Angular端请求必须携带凭证
在Angular的HttpClient请求中,需要显式设置withCredentials: true,否则浏览器不会在跨域请求中携带必要的头信息:
import { HttpClient, HttpHeaders } from '@angular/common/http'; // 假设你已经获取到了JWT令牌 const jwtToken = localStorage.getItem('jwtToken'); const headers = new HttpHeaders({ 'Authorization': `Bearer ${jwtToken}` }); // 发送请求时带上withCredentials this.http.get('http://localhost:8080/api/protected-data', { headers, withCredentials: true }).subscribe( response => console.log('请求成功:', response), error => console.error('请求失败:', error) );
最后排查点
- 确认浏览器控制台的CORS错误信息,看是
Origin不被允许,还是Access-Control-Allow-Credentials没设置 - 检查Spring Boot的日志,看OPTIONS请求是否被正确放行,还是被JWT过滤器拦截了
- 如果你的JWT令牌存在Cookie中,还要确保Cookie的
SameSite属性设置为Lax或者None(如果是跨域)
内容的提问来源于stack exchange,提问作者Stefan Radonjic
相关产品推荐
相关产品推荐

