Spring Boot 2.3 OAuth集成CORS过滤器遇预检请求失败求助
Spring Boot 2.3 OAuth应用CORS预请求失败问题排查
问题现象
Postman访问应用可正常获取认证密钥,但本地HTML文件通过jQuery发起Ajax请求时触发CORS错误:
Access to XMLHttpRequest at 'my sercice' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
现有代码
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsUtils; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import de.mtc.procon.authserver.provider.CustomAuthenticationProvider; @Configuration public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private CustomAuthenticationProvider customAuthenticationProvider; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } // @Bean // public CorsFilter corsFilter() { // return new MyCorsFilter(getMyCorsConfigurationSource()); // } @Override public void configure(HttpSecurity http) throws Exception { System.out.println("---------------------- Configure authentication server -----------------------"); http.cors().and().authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll().antMatchers("/**").authenticated().and().httpBasic().and().csrf().disable().exceptionHandling() .authenticationEntryPoint(new CustomAuthenticationEntryPoint()); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(customAuthenticationProvider); } @Qualifier @Primary @Bean public CorsConfigurationSource corsConfigurationSource() { System.out.println("Generating cors configuration source..."); return getMyCorsConfigurationSource(); } private CorsConfigurationSource getMyCorsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", getMyCorsConfiguration()); return source; } private CorsConfiguration getMyCorsConfiguration() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token")); configuration.setAllowCredentials(true); configuration.setMaxAge(Duration.ofDays(1)); configuration.setExposedHeaders(Arrays.asList("x-auth-token")); return configuration; } }
问题分析
你的包引用没有问题,使用的都是Spring官方org.springframework.web.cors下的标准类,排除包错误的可能。核心问题出在以下两点:
- CORS配置冲突:当设置
allowCredentials=true时,allowedOrigins不能使用通配符*,这是浏览器的安全限制,会导致CORS配置不生效。 - 预请求被拦截:
CustomAuthenticationEntryPoint可能对OPTIONS预请求返回了非200状态码,或者CORS过滤器优先级低于Spring Security,导致预请求先被Security拦截返回401。
解决方案
1. 修正CORS配置的凭证与Origin冲突
本地HTML文件的请求Origin为null,需明确允许该Origin,同时避免通配符与凭证的冲突:
private CorsConfiguration getMyCorsConfiguration() { CorsConfiguration configuration = new CorsConfiguration(); // 允许本地文件的null Origin及测试用的本地服务地址 configuration.setAllowedOrigins(Arrays.asList("null", "http://localhost:8080")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token")); configuration.setAllowCredentials(true); configuration.setMaxAge(Duration.ofDays(1)); configuration.setExposedHeaders(Arrays.asList("x-auth-token")); return configuration; }
2. 提升CORS过滤器优先级
注册CORS过滤器并设置最高优先级,确保它在Spring Security过滤器之前执行:
import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.filter.CorsFilter; // 新增该Bean定义 @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", getMyCorsConfiguration()); return new CorsFilter(source); }
3. 调整认证入口点,跳过OPTIONS请求
修改CustomAuthenticationEntryPoint,不对OPTIONS预请求返回未授权:
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { // 对OPTIONS预请求直接返回200 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); } else { // 原有认证失败处理逻辑 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); } } }
内容的提问来源于stack exchange,提问作者Felix H
相关产品推荐
相关产品推荐

