Spring Boot 自动配置中的 CorsConfigurationSource 为何不生效?
问题出在 @ConditionalOnMissingBean 的类型判断上:mvcHandlerMappingIntrospector 也实现了 CorsConfigurationSource,因此条件不成立,库中的默认 Bean 没有注册。可以用 ignored = HandlerMappingIntrospector.class 排除这个框架组件,同时继续检测应用提供的 CorsConfigurationSource。
修正条件后,还需要明确多个同类型 Bean 的注入选择,避免安全配置拿到 MVC 的 introspector。
为什么默认 Bean 没有注册?
在 @Bean 方法上直接使用 @ConditionalOnMissingBean 时,默认按方法返回类型检查已有 Bean。原方法返回 CorsConfigurationSource,条件报告显示已经找到 mvcHandlerMappingIntrospector,所以该方法被跳过。ConditionalOnMissingBean 文档
HandlerMappingIntrospector 实现了 CorsConfigurationSource,用于根据请求获取 Spring MVC 中的 CORS 配置。它的存在不代表应用已经提供了与公司配置属性对应的跨域规则,但类型条件无法区分这两种用途。HandlerMappingIntrospector 文档
日志表明 Bean 注册条件未满足。此时调整已注册 Bean 的优先级,无法让被跳过的方法重新生效。
修改自动配置中的条件
保留原自动配置类的其他内容,将 CORS Bean 方法替换为下面的写法。WebMvcSecurityProperties 和 Cors 沿用问题中的项目类型。
import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
// 放在原 WebMvcSecurityAutoConfiguration 类中
@Bean
@Primary
@ConditionalOnMissingBean(
value = CorsConfigurationSource.class,
ignored = HandlerMappingIntrospector.class
)
UrlBasedCorsConfigurationSource corsConfigurationSource(
WebMvcSecurityProperties properties) {
Cors cors = properties.cors();
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(cors.origins());
configuration.setAllowedHeaders(
cors.allowedHeaders() != null
? cors.allowedHeaders()
: List.of(CorsConfiguration.ALL));
configuration.setAllowedMethods(
cors.allowedMethods() != null
? cors.allowedMethods()
: List.of(CorsConfiguration.ALL));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
显式指定 value = CorsConfigurationSource.class 后,应用只要提供该接口的任意实现,默认配置就会退让。即使方法返回类型改成更具体的 UrlBasedCorsConfigurationSource,条件的检测范围也不会缩小。ignored 只负责从条件检查中排除 HandlerMappingIntrospector。条件注解参数说明
使用具体返回类型也方便框架在实例化前识别 Bean 类型。不过,原代码已经显式调用 .configurationSource(...),这次故障的直接原因仍是条件没有匹配。
明确安全配置注入哪个 Bean
ignored 不会删除 mvcHandlerMappingIntrospector,也不会把它排除在依赖注入之外。默认配置注册成功后,容器里仍可能有两个 CorsConfigurationSource 候选。
上面的 @Primary 让默认配置成为单值注入的首选。只要候选中恰好有一个 primary Bean,Spring 就会优先注入它。Spring 的 @Primary 说明
应用提供自定义配置时,也可以将自己的 Bean 标记为 @Primary。下面是一个独立的应用配置示例,域名和允许的方法需要按实际需求调整:
@Bean
@Primary
UrlBasedCorsConfigurationSource applicationCorsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("https://app.example.com"));
configuration.setAllowedMethods(List.of("GET", "POST"));
configuration.setAllowedHeaders(List.of("Authorization", "Content-Type"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
在正常通过 Spring Boot 自动配置机制加载的前提下,应用配置中的这个 Bean 会使库的默认 Bean 退让。这里按类型检测,因此它不必与默认 Bean 同名,也无需开启同名 Bean 覆盖。
原来的安全链可以继续显式传入注入的对象:
.cors(configurer ->
configurer.configurationSource(corsConfigurationSource))
这种写法直接指定当前 SecurityFilterChain 使用的配置源。如果应用为不同安全链定义了多个配置源,可以通过 @Qualifier 分别选择,再传给各自的 .configurationSource(...)。Spring Security CORS 配置说明
为什么调整顺序没有解决问题?
@AutoConfigureBefore(SecurityAutoConfiguration.class) 只声明与指定自动配置类之间的先后关系,不能保证条件判断时不存在 MVC 注册的 Bean。自动配置顺序影响 Bean 定义的处理顺序,也不等同于 Bean 的实例化顺序。AutoConfigureBefore 文档
既然条件失败的原因已经明确,应先修正检测范围。单独添加 @Order(HIGHEST_PRECEDENCE) 无法改变 HandlerMappingIntrospector 实现了该接口这一事实。
如何确认修正生效?
分别检查两种启动场景:
- 应用没有自定义配置源时,默认 CORS Bean 应当注册,条件报告不再因
mvcHandlerMappingIntrospector而拒绝它。 - 应用提供自定义配置源时,默认 Bean 应当退让,安全链注入应用选定的配置源。
随后使用带 Origin 和 Access-Control-Request-Method 的 OPTIONS 预检请求检查实际 CORS 响应。Bean 注册成功只能证明条件问题已解决,具体跨域行为还取决于来源、方法和请求头配置。
原代码设置了 allowCredentials(true),此时 allowedOrigins 不能包含 "*"。应配置可信的具体来源;如确需来源模式匹配,再按需求使用 allowedOriginPatterns。CorsConfiguration 文档
备注:内容仅供参考。
