You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 18:20:58