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

Spring Boot 3.x+Azure AAD升级后CORS失效,原2.6版本正常

Spring Boot 3.x + Azure AAD 5.4 升级后CORS失效问题排查

我们的应用需自定义OIDCUserService、OIDCUser对象,并结合Spring配置Azure AAD安全策略。Spring Boot 2.6版本下配置可正常运行,但升级至Spring Boot 3.x与Azure 5.4后,改用新配置类重构代码,CORS功能失效,已耗时两周仍未解决,现排查配置问题。


旧版本(Spring Boot 2.6)正常配置代码

@EnableWebSecurity
class AADServerConfig {

@Order(1)
@Configuration
static class ApiWebSecurityConfigurationAdapter extends AadResourceServerWebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http)
        http
                .cors()
                .and()
                .csrf().disable()
                .antMatcher("/api/**")
                .authorizeRequests().anyRequest().authenticated()

        http
                .headers()
                .frameOptions()
                .disable()
                .httpStrictTransportSecurity()
                .disable()
    }
}

@Configuration
class HtmlWebSecurityConfigurerAdapter extends AadWebSecurityConfigurerAdapter {
    @Autowired
    private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
    @Autowired
    private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;
    @Autowired
    private OAuth2HttpCookieAuthorizationRequestRepository oAuth2HttpCookieAuthorizationRequestRepository;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http)
        http
                .oauth2Login()
                .authorizationEndpoint()
                .authorizationRequestRepository(oAuth2HttpCookieAuthorizationRequestRepository)
                .and()
                .successHandler(oAuth2AuthenticationSuccessHandler)
                .failureHandler(oAuth2AuthenticationFailureHandler)

        http
                .cors()
                .and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/", "/login", "/*.js", "/*.css", "/token", "/actuator/**", "/actuator/prometheus", "/version/**", "/docusign/events/**").permitAll()
                .anyRequest().authenticated()

        http
                .headers()
                .frameOptions()
                .disable()
                .httpStrictTransportSecurity()
                .disable()
    }
}

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration cors = new CorsConfiguration()
    cors.setAllowedOrigins([
            "http://localhost:3000",
            "http://localhost:3001",
            "http://localhost:8080",
            "https://localhost:8080",
            "https://dev-local.panoram.co"
    ])
    cors.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE", "OPTIONS", "FETCH", "PATCH"))
    cors.setAllowCredentials(true)
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource()
    source.registerCorsConfiguration("/**", cors.applyPermitDefaultValues())
    return source
}
}

升级后(Spring Boot 3.x + Azure 5.4)重构代码(CORS失效)

@EnableWebSecurity
class AADServerConfig {

@Order(1)
@Configuration
//com.azure.spring.cloud.autoconfigure.aad.AadResourceServerWebSecurityConfigurerAdapter
class ApiWebSecurityConfigurationAdapter extends AadResourceServerHttpSecurityConfigurer {
    @Override
    void configure(HttpSecurity http) throws Exception {
        super.configure(http)
        http
                .cors(corsCustomizer -> {
                    corsCustomizer.configurationSource(corsConfigurationSource())
                })
                .csrf(csrfConfig -> {
                    csrfConfig.disable()
                })
                .securityMatcher("/api/**")
                .authorizeHttpRequests {}(requestsCustomizer -> {
                    requestsCustomizer.anyRequest().authenticated()
                })

        http
                .headers(header -> {
                    header.frameOptions {frameOptions -> {
                        frameOptions.disable()
                    }}
                    header.httpStrictTransportSecurity {transportConfig ->
                        transportConfig.disable()
                    }
                })
    }
}

@Configuration
class HtmlWebSecurityConfigurerAdapter extends AadWebApplicationHttpSecurityConfigurer {
    @Autowired
    private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
    @Autowired
    private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;
    @Autowired
    private OAuth2HttpCookieAuthorizationRequestRepository oAuth2HttpCookieAuthorizationRequestRepository;

    @Override
    void configure(HttpSecurity http) throws Exception {
        super.configure(http)
        http
                .oauth2Login(customizer -> {
                    customizer.authorizationEndpoint {endpointConfig -> {
                        endpointConfig.authorizationRequestRepository(oAuth2HttpCookieAuthorizationRequestRepository)
                    }}
                    customizer.successHandler {oAuth2AuthenticationSuccessHandler}
                    customizer.failureHandler {oAuth2AuthenticationFailureHandler}
                })

        http
                .cors(corsCustomizer -> {
                    corsCustomizer.configurationSource(corsConfigurationSource())
                })
                .csrf(csrfConfig -> {
                    csrfConfig.disable()
                })
                .authorizeHttpRequests(authz ->{
                    authz.requestMatchers("/", "/login", "/*.js", "/*.css", "/token", "/actuator/**", "/actuator/prometheus", "/version/**", "/docusign/events/**").permitAll()
                            .anyRequest().authenticated()
                })

        http.headers(header -> {
            header.frameOptions {frameOptions -> {
                frameOptions.disable()
            }}
            header.httpStrictTransportSecurity {transportConfig ->
                transportConfig.disable()
            }
        })
    }
}

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration()
    configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000",
            "https://localhost:3000",
            "http://localhost:3001",
            "https://localhost:3001",
            "https://localhost:5001",
            "http://localhost:5001",
            "http://localhost:8080",
            "https://localhost:8080",
            "https://dev-local.panoram.co"))
    configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE", "OPTIONS", "FETCH", "PATCH"))
    configuration.setAllowCredentials(true)
    CorsConfigurationSource source = new UrlBasedCorsConfigurationSource()
    source.registerCorsConfiguration("/**", configuration.applyPermitDefaultValues())
    return source
}
}

浏览器控制台错误日志

浏览器控制台日志


配置问题分析与修复方案

核心问题点

  1. 配置类继承方式错误:AadResourceServerHttpSecurityConfigurer和AadWebApplicationHttpSecurityConfigurer是配置器工具类,而非WebSecurityConfigurerAdapter的替代类,不能直接继承作为配置类使用。Spring Boot 3.x中推荐基于SecurityFilterChain Bean的组件式配置。

  2. SecurityMatcher顺序错误:在新配置中,.securityMatcher("/api/**")放在CORS、CSRF配置之后,导致CORS过滤器无法匹配到目标请求,规则不生效。

  3. CORS配置实例重复创建:两个配置类中手动调用corsConfigurationSource()创建新实例,未复用容器中已注册的Bean,导致配置不统一。

修正后的配置代码

@EnableWebSecurity
class AADServerConfig {

    @Autowired
    private CorsConfigurationSource corsConfigurationSource;

    @Order(1)
    @Configuration
    static class ApiWebSecurityConfiguration {

        @Autowired
        private CorsConfigurationSource corsConfigurationSource;

        @Bean
        public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
            // 应用Azure AAD资源服务器配置器
            http.apply(AadResourceServerHttpSecurityConfigurer.aadResourceServer())
                    .and()
                    .securityMatcher("/api/**") // 优先设置请求匹配范围
                    .cors(cors -> cors.configurationSource(corsConfigurationSource))
                    .csrf(csrf -> csrf.disable())
                    .authorizeHttpRequests(authz -> authz.anyRequest().authenticated())
                    .headers(headers -> headers
                            .frameOptions(frame -> frame.disable())
                            .httpStrictTransportSecurity(hsts -> hsts.disable()));

            return http.build();
        }
    }

    @Configuration
    static class HtmlWebSecurityConfiguration {

        @Autowired
        private CorsConfigurationSource corsConfigurationSource;
        @Autowired
        private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
        @Autowired
        private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;
        @Autowired
        private OAuth2HttpCookieAuthorizationRequestRepository oAuth2HttpCookieAuthorizationRequestRepository;

        @Bean
        public SecurityFilterChain htmlSecurityFilterChain(HttpSecurity http) throws Exception {
            // 应用Azure AAD Web应用配置器
            http.apply(AadWebApplicationHttpSecurityConfigurer.aadWebApplication())
                    .and()
                    .cors(cors -> cors.configurationSource(corsConfigurationSource))
                    .csrf(csrf -> csrf.disable())
                    .oauth2Login(oauth2 -> oauth2
                            .authorizationEndpoint(endpoint -> endpoint
                                    .authorizationRequestRepository(oAuth2HttpCookieAuthorizationRequestRepository))
                            .successHandler(oAuth2AuthenticationSuccessHandler)
                            .failureHandler(oAuth2AuthenticationFailureHandler))
                    .authorizeHttpRequests(authz -> authz
                            .requestMatchers("/", "/login", "/*.js", "/*.css", "/token", "/actuator/**", "/actuator/prometheus", "/version/**", "/docusign/events/**").permitAll()
                            .anyRequest().authenticated())
                    .headers(headers -> headers
                            .frameOptions(frame -> frame.disable())
                            .httpStrictTransportSecurity(hsts -> hsts.disable()));

            return http.build();
        }
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList(
                "http://localhost:3000",
                "https://localhost:3000",
                "http://localhost:3001",
                "https://localhost:3001",
                "https://localhost:5001",
                "http://localhost:5001",
                "http://localhost:8080",
                "https://localhost:8080",
                "https://dev-local.panoram.co"
        ));
        configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE", "OPTIONS", "FETCH", "PATCH"));
        configuration.setAllowCredentials(true);
        configuration.applyPermitDefaultValues();
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

关键修正说明

  • 替换继承逻辑:通过http.apply()方法应用Azure的配置器,符合Spring Boot 3.x的安全配置规范。
  • 调整匹配器顺序:将securityMatcher移至配置最前端,确保CORS等过滤器优先作用于目标请求。
  • 复用CORS配置Bean:注入容器中已定义的CorsConfigurationSource,保证配置一致性。
  • 返回SecurityFilterChain:每个配置类构建独立的SecurityFilterChain Bean,Spring按@Order优先级处理不同路径的请求。

内容的提问来源于stack exchange,提问作者bytor99999

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 15:14:49