Spring Boot 3 + JWT认证接口CORS问题修复方案求助
修复Spring Boot 3 + JWT场景下的CORS问题
核心原因
使用Spring Security(JWT认证依赖该框架)时,Security的过滤器链优先级高于普通CORS配置,导致通过WebMvcConfigurer、CorsWebFilter或@CrossOrigin配置的规则被提前拦截,无法生效。必须将CORS规则集成到Security配置中才能解决问题。
可行修复方案
1. 定义全局CORS配置类
创建配置类,设置允许所有来源、请求方法及头信息的规则:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.List; @Configuration public class CorsConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); // 允许所有来源(Spring Boot 2.4+推荐用AllowedOriginPatterns支持通配符) config.setAllowedOriginPatterns(List.of("*")); // 允许所有HTTP请求方法 config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许所有请求头(包括JWT的Authorization头) config.setAllowedHeaders(List.of("*")); // 允许携带凭证(如需前端传递Cookie等) config.setAllowCredentials(true); // 暴露自定义响应头(比如刷新Token的返回头) config.setExposedHeaders(List.of("Authorization")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // 对所有接口路径应用配置 source.registerCorsConfiguration("/**", config); return source; } }
2. 在Spring Security配置中启用CORS
修改Security配置类,确保CORS过滤器在认证过滤器之前执行,并放行预检OPTIONS请求:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfigurationSource; @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CorsConfigurationSource corsConfigurationSource; // 构造注入依赖 public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, CorsConfigurationSource corsConfigurationSource) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; this.corsConfigurationSource = corsConfigurationSource; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 启用CORS,绑定上述全局配置 .cors(cors -> cors.configurationSource(corsConfigurationSource)) // JWT场景下禁用CSRF .csrf(csrf -> csrf.disable()) // 无状态会话配置 .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 权限规则配置 .authorizeHttpRequests(auth -> auth // 放行所有OPTIONS预检请求(浏览器跨域非简单请求必发) .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 放行登录、注册等无需认证的接口 .requestMatchers("/api/auth/**").permitAll() // 其余接口需认证 .anyRequest().authenticated() ) // 将JWT过滤器添加到Security过滤器链的指定位置 .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }
3. 清理冲突配置
- 删除之前通过
WebMvcConfigurer实现的跨域配置 - 移除接口上的
@CrossOrigin注解(全局配置已覆盖) - 删除独立的
CorsWebFilterBean(避免与Security的CORS配置冲突)
前端请求示例
Axios示例
import axios from 'axios'; // 创建全局Axios实例 const apiClient = axios.create({ baseURL: 'http://你的后端域名/api', timeout: 10000, }); // 请求拦截器:自动添加JWT Token到请求头 apiClient.interceptors.request.use(config => { const token = localStorage.getItem('jwtToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => Promise.reject(error)); // 调用需要认证的接口示例 async function fetchUserInfo() { try { const response = await apiClient.get('/user/info'); console.log('用户信息:', response.data); } catch (error) { console.error('请求失败:', error.response?.data || error.message); } } // 登录接口调用示例(无需认证) async function userLogin(username, password) { try { const response = await apiClient.post('/auth/login', { username, password }); localStorage.setItem('jwtToken', response.data.token); alert('登录成功'); } catch (error) { console.error('登录失败:', error.response?.data || error.message); } }
Fetch示例
// 带JWT的GET请求示例 async function fetchUserInfo() { const token = localStorage.getItem('jwtToken'); try { const response = await fetch('http://你的后端域名/api/user/info', { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!response.ok) throw new Error(`HTTP错误:${response.status}`); const data = await response.json(); console.log('用户信息:', data); } catch (error) { console.error('请求失败:', error.message); } } // 登录请求示例 async function userLogin(username, password) { try { const response = await fetch('http://你的后端域名/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); if (!response.ok) throw new Error(`HTTP错误:${response.status}`); const data = await response.json(); localStorage.setItem('jwtToken', data.token); alert('登录成功'); } catch (error) { console.error('登录失败:', error.message); } }
内容的提问来源于stack exchange,提问作者Achraf Ait Ibba
相关产品推荐
相关产品推荐

