Spring Security(v3.2.0)下CORS跨域配置失效问题求助
Spring Security + Spring Boot 跨域CORS问题排查
背景
使用Spring Security和Spring Boot搭建资源中间服务,架构为:LandingPage(VM1)→ API服务(SpringApp)→ 带数据的资源服务(VM_N)。通过POSTMAN调用API正常,但浏览器中遇到CORS跨域问题(已知POSTMAN不受CORS限制)。
初始Security配置
@Configuration @EnableWebSecurity @EnableMethodSecurity @RequiredArgsConstructor public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { LOGGER.info("securityFilterChain "); http .csrf(Customizer.withDefaults()) // .addFilterBefore(corsFilter(), CorsFilter.class) .httpBasic(Customizer.withDefaults()) .formLogin(Customizer.withDefaults()) // .cors((cors) -> cors // .configurationSource(corsConfigurationSource())) .authorizeRequests(authorize -> authorize .requestMatchers() .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .requestMatchers(HttpMethod.OPTIONS).permitAll() .requestMatchers("/info/**").permitAll() .requestMatchers("/register/**").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oAuth -> oAuth.jwt(jwt -> { jwt.decoder(jwtDecoder()); jwt.jwtAuthenticationConverter(jwtAuthConverter); })) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); return http.build(); } @Bean public CorsFilter corsFilter() { CorsFilter corsFilter = new CorsFilter(corsConfigurationSource()); return corsFilter; } @Bean public CorsConfigurationSource corsConfigurationSource() { LOGGER.info("CORS config load"); CorsConfiguration configuration = new CorsConfiguration(); configuration.addAllowedOriginPattern(landing); // Landing VM configuration.addAllowedHeader("Authorization, Origin, X-Requested-With, Content-Type, Accept, " + "Access-Control-Allow-Headers, Access-Control-Request-Method, Access-Control-Request-Headers"); configuration.addAllowedMethod("HEAD, GET, PUT, POST, OPTIONS"); configuration.addExposedHeader("Authorization"); configuration.setAllowCredentials(true); CorsConfiguration publicEndpointConfig = new CorsConfiguration(); publicEndpointConfig.addAllowedOriginPattern(landing); // Landing VM publicEndpointConfig.addAllowedOriginPattern(gateway); // Gateway VM publicEndpointConfig.addAllowedOriginPattern(php); // PHP VM publicEndpointConfig.addAllowedHeader("Authorization, Origin, X-Requested-With, Content-Type, Accept"); publicEndpointConfig.addAllowedMethod("HEAD, GET, PUT, POST, OPTIONS"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/info/**", publicEndpointConfig); source.registerCorsConfiguration("/register/**", publicEndpointConfig); source.registerCorsConfiguration("/**", configuration); return source; } }
注:已注释
addFilterBefore和cors(),因对应用无明显影响。
Controller代码
@RequestMapping(value = "**", method = RequestMethod.OPTIONS) @CrossOrigin(origins = landing) public ResponseEntity<String> handleOptions() { LOGGER.info("INFO handle general OPTIONS requests"); HttpHeaders headers = new HttpHeaders(); headers.add("Access-Control-Allow-Origin", landing ); headers.add("Access-Control-Allow-Methods", "HEAD, GET, POST, PUT, OPTIONS, DELETE"); headers.add("Access-Control-Allow-Credentials", "true"); headers.add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, Content-Type, Accept"); headers.add("Access-Control-Max-Age", "3600"); return ResponseEntity.ok().headers(headers).body("handle OPTIONS requests"); } @RequestMapping(value = "**", method = RequestMethod.GET) @CrossOrigin(origins = landing) public ResponseEntity<String> getAnythingElse(Authentication authentication, HttpServletRequest request) { LOGGER.info("user with no authority or role, getAnythingElse"); String tokenValue = null; String phpUri = null; LOGGER.debug("Authentication is: " + authentication); if (authentication != null && authentication.isAuthenticated()) { JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication; Jwt jwt = jwtAuthenticationToken.getToken(); tokenValue = jwt.getTokenValue(); LOGGER.debug("Token is: " + tokenValue); } String requestUrl = request.getRequestURL().toString(); LOGGER.info("Request: " + requestUrl); String regexPattern = ".com/(.*)"; Pattern pattern = Pattern.compile(regexPattern); Matcher matcher = pattern.matcher(requestUrl); if (matcher.find()) { phpUri = matcher.group(1); } // 创建HttpHeaders并将token添加到请求头 HttpHeaders headers = new HttpHeaders(); headers.add("Access-Control-Allow-Origin", landing ); headers.add("Access-Control-Allow-Methods", "HEAD, GET, POST, PUT, OPTIONS"); headers.add("Access-Control-Allow-Credentials", "true"); headers.add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, Content-Type, Accept"); headers.add("Access-Control-Max-Age", "3600"); headers.set("Authorization", "Bearer " + tokenValue); // 创建包含headers的HttpEntity HttpEntity<String> entity = new HttpEntity<>(headers); // 创建RestTemplate实例 RestTemplate restTemplate = new RestTemplate(); LOGGER.info("完整URL: " + (phpApiUrl + phpUri)); // 调用PHP API ResponseEntity<String> response = restTemplate.exchange( phpApiUrl + phpUri, // PHP API地址 HttpMethod.GET, // 请求方法 entity, // 包含请求头的实体 String.class // 响应类型 ); // 返回响应内容 return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(response.getBody()); }
遇到的CORS错误
- 跨源请求被阻止:同源策略禁止读取位于 https://springApp/login/user_info 的远程资源。(原因:CORS请求未成功)。状态码:(null)。
- 跨源请求被阻止:同源策略禁止读取位于 https://springApp/issue_tracker/?format=json 的远程资源。(原因:根据CORS预检响应中的头信息
Access-Control-Allow-Headers,头cache-control不被允许)。 - (原因:缺少CORS头
Access-Control-Allow-Origin)。 - 跨源请求被阻止:同源策略禁止读取位于 https://springApplication/storage/count 的远程资源。(原因:CORS头
Access-Control-Allow-Credentials中预期值为"true")。
根据Spring文档,当前配置应满足需求,但实际无效,恳请帮忙排查问题。
更新后的配置及新错误
调整代码后出现新错误:
跨源请求被阻止:同源策略禁止读取位于 https://springApp/user_info 的远程资源。(原因:CORS预检响应未成功)。状态码:403。
更新后的Security配置代码:
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .headers(header -> header.addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Origin", landing))); http .headers(header -> header.addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Credentials", "true"))); http .csrf(Customizer.withDefaults()) .authorizeRequests(authorize -> authorize .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers("/info/**").permitAll() .requestMatchers("/register/**").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oAuth -> oAuth.jwt(jwt -> { jwt.decoder(jwtDecoder()); jwt.jwtAuthenticationConverter(jwtAuthConverter); })) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .httpBasic(Customizer.withDefaults()) .formLogin(Customizer.withDefaults()) ; return http.build(); }
内容的提问来源于stack exchange,提问作者Alexander Chadfield
相关产品推荐
相关产品推荐

