一、 背景与痛点
在传统的 Spring Security 开发中(尤其是单体大应用),我们往往会在一个主配置类(如 SecurityConfig)里写死所有的 URL 权限规则:
java
// 传统写法:随着业务增长,这个方法会变成几百行的"面条代码"
http.authorizeHttpRequests()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/order/**").hasRole("USER")
.requestMatchers("/pay/**").permitAll()
// ... 无休止的追加 ...
痛点:
- 严重耦合:基础架构层必须感知所有业务模块的 URL 规则。
- 维护困难:多人开发时,大家都在修改同一个文件,代码冲突不断。
- 扩展性差:新增一个业务模块,必须去改主工程的代码。
二、 核心架构设计
为了解决上述问题,我们引入了 "插拔式" 的设计模式。核心由三个部分组成:
- 调度中心:主配置类(只负责调度,不负责具体规则)。
- 标准协议 :
AuthorizeRequestsCustomizer抽象类(定义怎么配)。 - 业务实现:各模块的 Customizer(具体配什么)。
1. 调度中心:主 SecurityFilterChain
在主配置类(如 YudaoWebSecurityConfigurerAdapter)中,我们不再硬编码规则,而是利用 Spring 的 自动注入(Dependency Injection) 特性。
java
// 1. 注入所有实现了 Customizer 接口的 Bean
@Resource
private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// ... 其他配置 ...
.authorizeHttpRequests(c -> {
// 2. 核心逻辑:遍历所有注入的 Customizer,让它们自己定义规则
authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c));
// 3. 兜底规则(最后执行)
c.anyRequest().authenticated();
});
return httpSecurity.build();
}
解析 :主配置类变成了一个"容器",它根本不知道 /order 需要什么权限,它只负责把话筒交给各个业务模块,让模块自己"发言"。
2. 标准协议:AuthorizeRequestsCustomizer
我们需要定义一个抽象类,既作为统一的接口类型,又可以提供一些通用的工具方法(如 API 前缀处理)。
java
public abstract class AuthorizeRequestsCustomizer
implements Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry>, Ordered {
@Resource
private WebProperties webProperties;
// 提供通用方法的封装,避免各模块硬编码前缀
protected String buildAdminApi(String url) {
return webProperties.getAdminApi().getPrefix() + url;
}
protected String buildAppApi(String url) {
return webProperties.getAppApi().getPrefix() + url;
}
// 默认优先级,业务模块可以通过重写此方法调整自己在过滤器链中的位置
@Override
public int getOrder() {
return 0;
}
}
3. 业务实现:模块化的 Customizer
假设我们有一个 "基础设施模块 (Infra)",它需要开放 Swagger 文档和一些监控断点,我们不需要改主工程,只需在 Infra 模块内部写一个 Bean:
java
@Configuration
public class InfraSecurityConfiguration {
@Bean("infraAuthorizeRequestsCustomizer")
public AuthorizeRequestsCustomizer infraAuthorizeRequestsCustomizer() {
return new AuthorizeRequestsCustomizer() {
@Override
public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
// 定义该模块独有的权限规则
registry.requestMatchers(buildAdminApi("/infra/file/**")).permitAll() // 文件下载免登录
.requestMatchers("/swagger-ui/**").permitAll() // Swagger 免登录
.requestMatchers("/druid/**").hasRole("ADMIN"); // 数据库监控需管理员
}
// 可选:如果需要在其他规则之前生效,可以调高优先级
@Override
public int getOrder() {
return -10;
}
};
}
}
三、 工作原理深度解析
这个机制之所以能工作,依赖于 Spring 容器强大的生命周期管理:
- 启动扫描 (Scanning) :
Spring Boot 启动时,扫描所有加了@Configuration的类。 - Bean 注册 (Registration) :
各个业务模块(Infra, Order, Pay)定义的AuthorizeRequestsCustomizer被实例化并注册到 Spring 容器中。 - 依赖收集 (Collection) :
当初始化主配置类YudaoWebSecurityConfigurerAdapter时,@Resource private List<AuthorizeRequestsCustomizer> list这行代码会触发 Spring 去容器里查找所有类型为AuthorizeRequestsCustomizer的 Bean,并将它们装进一个 List 集合中。 - 规则应用 (Application) :
在构建SecurityFilterChain时,代码遍历这个 List,依次调用customize()方法。 - 最终生效 (Finalization) :
Spring Security 将这些分散定义的规则合并成一个完整的RequestMatcher链条。
四、 优缺点总结
优点
- 开闭原则 (Open/Closed Principle):对扩展开放(新增模块只需加新 Bean),对修改关闭(无需动主配置)。
- 高内聚:业务模块的权限规则写在业务模块内部,代码物理距离更近,更容易理解。
- 灵活性 :通过
Ordered接口,可以精确控制规则的生效顺序(例如:通用黑名单规则优先级最高,普通业务规则优先级居中,兜底规则优先级最低)。
注意事项
- 顺序问题 :Spring Security 的匹配原则是 "先匹配生效(First Match Wins)" 。如果一个优先级高的 Customizer 配置了
/**->permitAll,那么后面所有模块的规则都会失效。因此使用Ordered进行顺序管理至关重要。