升级Spring Boot 3后集成OKTA遇CORS错误:缺少指定响应头
问题背景
我们的应用前端基于Vue.js,后端使用Spring Boot,通过OKTA实现安全认证。在Java 11 + Spring Boot 2.1.8环境下运行正常,后端REST服务地址为http://localhost:7801,前端NGINX地址为http://localhost:7800。
升级至Spring Boot 3.1.3 + Java 17后,前端访问接口时出现跨域错误:
跨域请求被阻止:同源策略不允许读取http://localhost:7801/oauth2/authorization/okta的远程资源。(原因:缺少CORS头‘Access-Control-Allow-Origin’)。状态码:403。
已完成Spring Security的适配调整,其余后端代码未修改,且OKTA中已配置http://localhost:7800和http://localhost:7800/作为可信来源。
旧版本(Spring Boot 2.1.8)配置
Security配置
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class OAuthSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .cors() .and().csrf().disable() .authorizeRequests() .antMatchers("/actuator/**").permitAll() .anyRequest().authenticated() .and().oauth2Client() .and().oauth2Login(); } }
CORS配置
@Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry .addMapping("/api/myapp/**") .allowedMethods("GET","POST","PUT","DELETE") .allowedOrigins("http://localhost:7800"); } }; }
新版本(Spring Boot 3.1.3)配置
Security配置
@Configuration @EnableWebSecurity @EnableMethodSecurity public class OAuthSecurityConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests((authz) -> authz .requestMatchers("/actuator/**").permitAll() .anyRequest().authenticated()) .oauth2Client(Customizer.withDefaults()) .oauth2Login(Customizer.withDefaults()); // done return http.build(); } }
CORS配置
@Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(List.of("http://localhost:7800")); configuration.setAllowedMethods(List.of("GET","POST","PUT","DELETE")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/api/myapp/**", configuration); return source; } // end corsConfigurationSource()
请求与响应头信息
请求头(OPTIONS请求)
OPTIONS /oauth2/authorization/okta HTTP/1.1 Host: localhost:7801 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Access-Control-Request-Method: GET Access-Control-Request-Headers: authorization Referer: http://localhost:7800/ Origin: http://localhost:7800 Connection: keep-alive Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-site
响应头
HTTP/1.1 403 Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers X-Content-Type-Options: nosniff X-XSS-Protection: 0 Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Frame-Options: DENY Transfer-Encoding: chunked Date: Thu, 07 Sep 2023 12:45:00 GMT Keep-Alive: timeout=60 Connection: keep-alive
控制台错误信息
跨域请求被阻止:同源策略不允许读取http://localhost:7801/oauth2/authorization/okta的远程资源。(原因:缺少CORS头‘Access-Control-Allow-Origin’)。状态码:403。
跨域请求被阻止:同源策略不允许读取http://localhost:7801/oauth2/authorization/okta的远程资源。(原因:CORS请求未成功)。状态码:(null)。
解决方案
1. 调整CORS配置范围
当前CORS仅对/api/myapp/**生效,但报错的是/oauth2/authorization/okta端点,需要将CORS规则覆盖到该端点:
修改corsConfigurationSource方法:
@Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(List.of("http://localhost:7800")); // 允许OPTIONS请求(浏览器预检请求) configuration.setAllowedMethods(List.of("GET","POST","PUT","DELETE","OPTIONS")); // 允许请求中的Authorization头 configuration.setAllowedHeaders(List.of("Authorization", "Content-Type")); // 允许携带凭证(如果前端需要) configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // 同时覆盖API路径和OAuth2授权路径 source.registerCorsConfiguration("/api/myapp/**", configuration); source.registerCorsConfiguration("/oauth2/authorization/**", configuration); return source; }
2. 确保Security放行预检请求
在SecurityFilterChain中,需要明确允许OPTIONS请求通过,避免被认证拦截:
修改filterChain方法:
@Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests((authz) -> authz .requestMatchers("/actuator/**").permitAll() // 放行所有OPTIONS请求,用于CORS预检 .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .anyRequest().authenticated()) .oauth2Client(Customizer.withDefaults()) .oauth2Login(Customizer.withDefaults()); return http.build(); }
3. 验证OKTA配置
确认OKTA中的登录重定向URI和可信来源已包含http://localhost:7800,并且Spring Boot配置文件中的OKTA相关属性(如spring.security.oauth2.client.registration.okta.redirect-uri)与前端地址匹配。
原理说明
Spring Boot 3中Spring Security的CORS处理逻辑有所调整,预检OPTIONS请求会先经过Security过滤器链。如果未明确放行OPTIONS请求,会被anyRequest().authenticated()拦截返回403。同时,原CORS配置未覆盖OAuth2授权端点,导致该端点的跨域请求缺少必要的响应头。
内容的提问来源于stack exchange,提问作者user925406

