React-Python通过Spring Security对接Keycloak的CORS跨域问题
架构背景
localhost:3000的React-Python应用调用localhost:80的Spring-Java认证服务,该服务已对接localhost:4000的Keycloak服务器。
测试情况
直接访问认证服务对接Keycloak - 验证成功:
从localhost:80发起请求,能正常重定向到Keycloak认证页面,响应头和请求头均无异常。通过React应用跳转时触发CORS错误:
请求从http://localhost:3000/login重定向到http://localhost:80/oauth2/authorization/keycloak时,被浏览器CORS策略拦截,错误提示:No 'Access-Control-Allow-Origin' header is present on the requested resource。第一次重定向正常,第二次跳转触发该问题。
已配置代码
OAuth2配置
http.oauth2Login() .and() .exceptionHandling() // Specific entry point for failed authentication .authenticationEntryPoint(getEntryPoint()) .and() .oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()); http.addFilterAfter(corsFilter(), SessionManagementFilter.class); ...
CORS Filter实现
public class CorsFilter implements Filter { @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) servletResponse; HttpServletRequest request= (HttpServletRequest) servletRequest; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Allow-Headers", "*"); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Max-Age", "180"); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }
认证入口点配置
private AuthenticationEntryPoint getEntryPoint() { return (request, response, authException) -> { if (authException != null) { response.sendRedirect("/oauth2/authorization/keycloak"); } else { response.setStatus(HttpStatus.UNAUTHORIZED.value()); } }; }
诉求
实现localhost:3000通过认证服务正常重定向到Keycloak完成认证,解决上述CORS问题。
1. 调整CORS过滤器执行顺序
当前CORS过滤器添加在SessionManagementFilter之后,但Spring Security认证相关过滤器执行更早,导致重定向响应未被CORS处理就返回。需将CORS过滤器放在过滤器链最前端:
// 替换原addFilterAfter,改为前置添加 http.addFilterBefore(corsFilter(), UsernamePasswordAuthenticationFilter.class);
2. 修复CORS配置冲突
Access-Control-Allow-Origin: *与Access-Control-Allow-Credentials: true不兼容,浏览器禁止这种组合。需指定具体允许的Origin:
// 替换通配符为React应用地址 response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"); // 多Origin场景可动态判断: // String origin = request.getHeader("Origin"); // if (Arrays.asList("http://localhost:3000", "其他允许地址").contains(origin)) { // response.setHeader("Access-Control-Allow-Origin", origin); // }
3. 优化前端跳转方式
避免通过AJAX请求触发认证跳转,直接通过浏览器顶级导航跳转:
// React中跳转示例 window.location.href = "http://localhost:80/oauth2/authorization/keycloak";
顶级导航不受CORS策略限制,能正常触发重定向流程。
4. 改用Spring Security内置CORS配置
替换自定义Filter,使用Spring Security原生CORS支持,更适配其过滤器链:
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(Arrays.asList("http://localhost:3000")); config.setAllowedMethods(Arrays.asList("GET", "POST", "OPTIONS", "DELETE")); config.setAllowedHeaders(Arrays.asList("*")); config.setAllowCredentials(true); config.setMaxAge(180L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } // 在Security配置中启用 http.cors().configurationSource(corsConfigurationSource()) .and() .oauth2Login() .and() .exceptionHandling() .authenticationEntryPoint(getEntryPoint()) .and() .oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
5. 配置Keycloak客户端允许跨域
登录Keycloak控制台,找到对应客户端,在Web Origins字段添加http://localhost:3000和http://localhost:80,避免后续回调环节触发CORS错误。
内容的提问来源于stack exchange,提问作者TPL

