资讯动态

Spring Security源码解析:WebSecurityConfigurerAdapter配置原理

发布时间:2026/10/15 3:32:13 来源:尧图企业网站定制
1. 一个类看懂 Spring Security 的配置组装逻辑Spring Security 里WebSecurityConfigurerAdapter 大概是出场率最高的一个类。不管是教程还是公司项目基本都能看到这种写法继承 WebSecurityConfigurerAdapter重写 configure(HttpSecurity http)然后在里面配上一段 antMatchers 和 authorizeRequests。但绝大多数人在这样写的时候其实并不清楚这个类是怎么被加载的三个 configure 方法之间到底是什么关系为什么有些配置写了却没用。这篇文章就围绕这个类的源码来拆适合有基本 Spring Boot Web 开发经验、被自定义安全配置折磨过的读者。读完之后你至少能回答三个问题WebSecurityConfigurerAdapter 是谁实例化的它的三个 configure 方法执行顺序由谁决定多适配器共存时请求到底走了哪条过滤器链先说结论WebSecurityConfigurerAdapter 本质是一个适配器它把 Spring Security 的核心构建流程包装成了几个可重写的钩子方法。它的底层是 WebSecurityConfigurer 接口而 WebSecurityConfigurer 接口又继承了 SecurityConfigurerFilter, WebSecurity。这个泛型签名很关键它说明这套配置器的最终产出物是一个 Filter而这个 Filter 就是要注册到 Servlet 容器里的 springSecurityFilterChain。1.1 接口与抽象类的继承结构打开 Spring Security 5.x 的源码类声明是这样的public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer { private HttpSecurity http; private AuthenticationManagerBuilder authenticationBuilder; private AuthenticationConfiguration authenticationConfiguration; private AuthenticationManager authenticationManager; private UserDetailsService userDetailsService; private boolean disableDefaults; // ... }而接口的定义更简短public interface WebSecurityConfigurer extends SecurityConfigurerFilter, WebSecurity { AuthenticationManager authenticationManager() throws Exception; UserDetailsService userDetailsServiceBean() throws Exception; }这里能看到一个容易忽略的点WebSecurityConfigurer 接口只强制要求实现两个方法一个是获取 AuthenticationManager一个是获取 UserDetailsService。至于我们熟悉的三个 configure 方法其实不是接口定义的而是适配器自己加的扩展点。换句话说WebSecurityConfigurerAdapter 把接口没有覆盖到的、但绝大多数开发者需要的配置入口都做成了带默认实现的钩子你重写哪个哪个就生效。这样的设计非常符合适配器模式的本意不强制你实现全部方法而是给一个“还算能用”的默认实现你只需要针对差异部分做覆盖。1.2 适配器模式的真正含义你可以把 WebSecurityConfigurerAdapter 理解成一个“总包工头”。Spring Security 的底层有两条非常复杂的构建链路一条是 HttpSecurity负责组装过滤器链另一条是 WebSecurity负责把所有过滤器链汇总成一个 FilterChainProxy。正常情况下这两条链路有一大堆内部步骤普通开发者根本不需要关心。适配器做的就是把这些内部步骤藏起来对外只留几个口子configure(HttpSecurity)主战场配置 URL 授权规则、登录方式、CSRF 等。configure(AuthenticationManagerBuilder)配置用户存储、密码编码器、自定义认证逻辑。configure(WebSecurity)配置全局级过滤规则典型的如忽略静态资源。这三个方法各有各的调用时机和生命周期后面我会逐个展开。如果你只是背配置不看源码很容易把三者的边界搞混最常见的错误就是在 configure(WebSecurity) 里写 authorizeRequests或者在 configure(HttpSecurity) 里写 ignoring这都属于用错了口子。1.3 三个 configure 方法的角色划分用一句话概括三个方法的分工configure(HttpSecurity) 负责“单条过滤器链长什么样”。configure(AuthenticationManagerBuilder) 负责“认证管理器用什么数据源和策略”。configure(WebSecurity) 负责“哪些请求根本不进过滤器链”。其中 configure(WebSecurity) 的执行时机最早它在 WebSecurity 构建阶段就被调用configure(HttpSecurity) 的调用发生在 HttpSecurity 的构建阶段configure(AuthenticationManagerBuilder) 则在 getHttp() 创建 HttpSecurity 之前就会被触发因为 HttpSecurity 内部需要引用一个可用的认证管理器。这三者的执行顺序不是随意的而是被 WebSecurity 和 HttpSecurity 这两个构建器层层传递的自然结果。接下来我先深入看三个方法各自的源码实现再看启动装配全流程。2. 三大 configure 钩子逐段解析默认配置的来源2.1 configure(HttpSecurity)过滤器链的主战场先看默认实现。如果你继承 WebSecurityConfigurerAdapter 但一个方法都不重写Spring Security 会给你一套默认配置。它的逻辑就写在 getHttp() 方法里默认调用如下http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic();这段代码的意思是所有请求都要认证提供表单登录和 HTTP Basic 两种认证方式。Spring Boot 项目里如果不做任何安全配置启动后访问任意接口就会跳转到登录页原因就在这里。你重写 configure(HttpSecurity http) 时默认行为会被完全覆盖而不是叠加。所以很多人第一次写的时候容易踩坑自己只配了一个 matching 规则发现其他路径全都匿名可访问了因为没有再调用 anyRequest().authenticated()。从源码视角看HttpSecurity 本身是一个 SecurityBuilder它的构建过程会调用所有已注册的 AbstractHttpConfigurer 的 init 和 configure 方法。比如 authorizeRequests() 会注册一个 ExpressionUrlAuthorizationConfigurerformLogin() 会注册一个 FormLoginConfigurer。最终构建出来的产物是 DefaultSecurityFilterChain里面按顺序排着一堆过滤器。2.2 configure(AuthenticationManagerBuilder)认证管理器怎么建立这个方法的默认实现是空的protected void configure(AuthenticationManagerBuilder auth) throws Exception { }不要以为空实现就不重要。真正有意思的是它什么时候被调用以及调用完之后 AuthenticationManager 去了哪里。在 getHttp() 方法里有一段核心逻辑this.localConfigureAuthenticationBldr new AuthenticationManagerBuilder(objectPostProcessor); this.localConfigureAuthenticationBldr.parentAuthenticationManager(authenticationManager);这里创建了一个局部的 AuthenticationManagerBuilder并且把全局的 authenticationManager 设置为它的 parent。然后这段代码会立刻调用 configure(localConfigureAuthenticationBldr)configure(localConfigureAuthenticationBldr);也就是说你在 configure(AuthenticationManagerBuilder) 里写的 userDetailsService、passwordEncoder、authenticationProvider实际上作用于一个局部构建器。如果配置完成后这个局部构建器里有实际的认证数据源后续 getAuthenticationManager() 会优先用它构建出一个独立的 AuthenticationManager如果局部构建器是空的就 fallback 到全局那个 AuthenticationManager。这个设计让我经常跟人解释一个现象为什么使用多个适配器时在其中某个适配器里配置了 userDetailsService对另一个适配器的认证逻辑完全不生效。因为每个适配器都有自己独立的局部构建器它们的 parent 才是共享的全局 AuthenticationManager。2.3 configure(WebSecurity)一个经常被误用的入口第三个 configure 方法默认也是空的protected void configure(WebSecurity web) throws Exception { }它唯一常见的用途是Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers(/css/**, /js/**, /images/**); }这里要理解 ignoring 和 permitAll 的本质区别。permitAll 是“请求虽然进了过滤器链但经过授权规则时直接放行”。ignoring 是“请求根本不进 Spring Security 的过滤器链”像静态资源这种完全无状态的请求连 CSRF 过滤器都不需要经过。源码层面WebSecurity.ignoring() 会创建一个 IgnoredRequestConfigurer最终这些忽略规则会被收集到 FilterChainProxy 中作为第一批判断条件。请求到达时先检查是否命中 ignore 列表命中就直接跳过。而 permitAll 是在 AuthorizationFilter 或 FilterSecurityInterceptor 里通过投票器放行前者轻量得多。所以性能敏感、完全不需要安全上下文的路径用 ignoring 更合适但如果你希望在过滤器链里做一些统一处理比如打日志、加请求头那就不该 ignoring改用 permitAll 更合理。2.4 AuthenticationManager 的四种获取姿势读这个类源码时另一个高频困惑是 AuthenticationManager 到底怎么拿。适配器里相关的方法有好几个它们的用途完全不同方法作用使用场景authenticationManager()接口方法获取认证管理器内部框架调用一般不用getAuthenticationManager()从局部或全局构建器中解析认证管理器自定义配置时获取认证管理器authenticationManagerBean()显式暴露 AuthenticationManager 为 Spring Bean需要注入到自己的 Service 里configure(AuthenticationManagerBuilder)往局部构建器注册认证数据源最常用的用户配置入口其中 authenticationManagerBean() 的实现比较有意思它带了一个 Bean 注解编译后实际上等于主动声明一个 Bean。源码里有一段注释大意是只要这个方法被子类调用最终生成的 AuthenticationManager 会被暴露到容器中。如果你在项目里自定义了过滤器需要手动调用认证管理器做认证就可以继承适配器并写一个 authenticationManagerBean() 方法。需要特别注意的是一旦你重写了 authenticationManager() 方法返回的 AuthenticationManager 优先级更高configure(AuthenticationManagerBuilder) 里写的局部配置可能就不会生效。这是一个隐蔽的坑等看到多适配器那一节再细说。3. 启动时一路追踪从注解到 FilterChainProxy学源码不能只盯着类内部还得看它是怎么被装配起来的。我从 EnableWebSecurity 出发把启动时的完整链路走一遍。3.1 EnableWebSecurity 到底导入了什么WebSecurityConfigurerAdapter 本身不干活它需要被外部触发才会构建。触发点是 EnableWebSecurityImport({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class }) public interface EnableWebSecurity { }其中 WebSecurityConfiguration 是核心配置类它负责创建 springSecurityFilterChain Bean。SpringWebMvcImportSelector 负责在 WebMVC 场景下导入额外配置比如把认证信息注入到 JSP 标签或 MVC 参数解析器。这里有一个细节值得注意Spring Boot 项目通常不需要显式加 EnableWebSecurity因为 Spring Boot 的自动配置类在 classpath 存在 Spring Security 时会自动引入安全配置。但如果你在项目里手动加了 EnableWebSecurity自动配置的后缀逻辑会尊重你的显式声明避免重复。3.2 Spring Boot 自动配置的默认适配器如何生效Spring Boot 的 SpringBootWebSecurityConfiguration 里有一个很有意思的条件逻辑。它的作用是如果容器里没有任何 WebSecurityConfigurerAdapter 类型的 Bean就自动注册一个空的适配器让系统能跑起来并套用默认安全配置。大致逻辑如下Bean ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() { return new WebSecurityConfigurerAdapter() {}; }注意这里会创建一个匿名适配器方法体是空的。这意味着什么意味着一个只提供了默认行为的适配器会被加到构建流程里最终的默认配置就源于 2.1 节里那段 authorizeRequests formLogin httpBasic。只要你自定义了一个继承 WebSecurityConfigurerAdapter 的配置类这个条件装配就不再生效自动配置的默认适配器退出舞台。所以你写的适配器会完全接管安全策略这一点从 Bean 装配层面就定死了。3.3 WebSecurityConfiguration 的构建入口springSecurityFilterChain接下来看 WebSecurityConfiguration 的核心方法Bean(name springSecurityFilterChain) public Filter springSecurityFilterChain() throws Exception { boolean hasConfigurers webSecurityConfigurers ! null !webSecurityConfigurers.isEmpty(); if (!hasConfigurers) { WebSecurityConfigurerAdapter adapter new WebSecurityConfigurerAdapter() {}; webSecurityConfigurers.add(adapter); } for (SecurityConfigurerFilter, WebSecurity securityConfigurer : webSecurityConfigurers) { webSecurity.apply(securityConfigurer); } return webSecurity.build(); }这段代码是整套装配流程的中枢。webSecurityConfigurers 这个列表是 Spring 容器自动收集的所有类型为 SecurityConfigurerFilter, WebSecurity 的 Bean 会被注入到这个字段里。你自定义的继承 WebSecurityConfigurerAdapter 的配置类就是通过这一机制被收集进来的。然后 webSecurity.apply(configurer) 把每个配置器都挂到 WebSecurity 上。最后调用 webSecurity.build() 把整个配置流程跑起来返回一个 Filter即 springSecurityFilterChain。这个 Bean 会被 Servlet 容器注册为过滤器所有请求进来先经过它。没有配置器时代码里也会手动补一个空适配器这个兜底逻辑和 Spring Boot 自动配置里的兜底逻辑是双保险防止用户把配置环境搞坏。3.4 build() 的执行顺序init 与 configure 的先后逻辑SecurityBuilder 的 build 过程遵循一个固定模式先是 doBuild再是 init然后是 configure。WebSecurity 的 doBuild 会对每一个注册进来的 SecurityConfigurer 依次调用 init 和 configure。对适配器而言public void init(WebSecurity web) throws Exception { HttpSecurity http getHttp(); web.addSecurityFilterChainBuilder(http) .postBuildAction(() - { FilterSecurityInterceptor securityInterceptor http.getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); }); } Override public void configure(WebSecurity web) throws Exception { }init 阶段做了一件关键的事调用 getHttp()强制创建 HttpSecurity并把 HttpSecurity 作为一个 SecurityFilterChainBuilder 注册到 WebSecurity 中。configure(WebSecurity web) 保持空实现把扩展机会留给子类。这个顺序解释了为什么 configure(WebSecurity) 里的代码不参与 HttpSecurity 的构建。WebSecurity 的 configure 阶段在构建全局过滤器链而 HttpSecurity 的构建实际上已经在上一步 init 中被触发。所以在 WebSecurity.configure 里想改 HttpSecurity 的规则是做不到的。3.5 getHttp() 创建 HttpSecurity 的关键细节再回头看 getHttp()它是理解适配器的核心瓶颈。方法内部首先是缓存判断if (this.http ! null) { return this.http; }同一个适配器实例HttpSecurity 只创建一次避免重复构建。接着创建局部 AuthenticationManagerBuilderAuthenticationEventPublisher eventPublisher getAuthenticationEventPublisher(); this.localConfigureAuthenticationBldr new AuthenticationManagerBuilder(objectPostProcessor); this.localConfigureAuthenticationBldr.parentAuthenticationManager(authenticationManager) .authenticationEventPublisher(eventPublisher);这里的 objectPostProcessor 是 Spring 容器里的后处理器所有内部创建的对象都会经过它以便进行 AOP 增强或者自定义处理。parentAuthenticationManager 把全局认证管理器设置为局部构建器的父级这样局部构建器即使没有任何数据源也能通过父级完成认证。紧接着调用 configure(localConfigureAuthenticationBldr)就会触发你重写的 configure(AuthenticationManagerBuilder) 方法。之后创建 HttpSecurityHttpSecurity http new HttpSecurity(objectPostProcessor, localConfigureAuthenticationBldr, sharedObjects);注意这个构造函数的参数objectPostProcessor 负责后处理所有过滤器localConfigureAuthenticationBldr 负责在过滤器链需要认证管理器时提供实例sharedObjects 是共享对象池各配置器之间通过它传数据。创建完后默认配置开始注入http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();然后调用 configure(http)让你重写的内容覆盖默认规则。最后记住实例并返回。所以整条链路是WebSecurity.init - 适配器.init - getHttp - 创建局部 AuthenticationManagerBuilder - 调用 configure(AuthenticationManagerBuilder) - 创建 HttpSecurity - 调用 configure(HttpSecurity)。4. 多适配器共存SecurityFilterChain 的匹配顺序与陷阱4.1 一个适配器对应一条过滤器链很多项目不止一个适配器尤其是前后端分离加上管理后台时会拆出好几套安全规则。你要清楚每个 WebSecurityConfigurerAdapter 实例都会通过 init 阶段注册一个 HttpSecurity而每个 HttpSecurity 构建出一个 DefaultSecurityFilterChain。这就意味着你有几个适配器就有几条过滤器链。最终它们都被放在 FilterChainProxy 里由它统一调度。DefaultSecurityFilterChain 内部保存了两样东西一个 RequestMatcher 和一个 List。RequestMatcher 负责判断当前请求是否命中这条链Filter 列表则是真正干活的安全过滤器。4.2 Order 排序与先匹配先服务多适配器共存时顺序非常重要。WebSecurityConfiguration 收集配置器列表后会通过 AnnotationAwareOrderComparator 进行排序。默认情况下WebSecurityConfigurerAdapter 上标记了一个 Order(100)如果你没有显式指定多个适配器的相对顺序就是不确定的这会带来随机性的 bug。请求进来时FilterChainProxy 会按顺序遍历所有 DefaultSecurityFilterChain命中第一个 RequestMatcher 匹配的链就使用它后面的链完全不执行。这跟 Spring MVC 里拦截器的顺序认知完全不同。MVC 拦截器是按顺序都执行一遍安全过滤器链则是遇到第一个匹配就短路。所以当你配了多个适配器一定要给每个适配器明确的 Order否则两个适配器的 antMatcher 规则一旦重叠谁先执行完全没有保障。4.3 antMatcher 与 authorizeRequests 的混淆点这是我在实际项目中遇到最多的问题。看这段错误配置Configuration Order(1) public class ApiSecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(/api/**).hasRole(ADMIN) .anyRequest().authenticated(); } }很多人以为这是“只对 /api/** 生效的适配器”其实不是。适配器没写 http.antMatcher(/api/**) 时它的 RequestMatcher 是匹配所有请求的。也就是说任何请求只要走到这条链都会进入这条链的授权规则找不到匹配规则时落到 anyRequest().authenticated()。正确的做法是在链的层面先限定匹配范围http.antMatcher(/api/**) .authorizeRequests() .antMatchers(/api/**).hasRole(ADMIN) .anyRequest().authenticated();这样 /api/** 以外的请求会跳到下一条链不会在这里被 anyRequest 拦下来。两者一个是链的入口条件一个是链内部的 URL 级授权规则千万别混。4.4 多适配器下 AuthenticationManager 的归属问题多适配器下另一个诡异问题是认证管理器归属。刚才讲过每个适配器的 getHttp() 会创建独立的局部 AuthenticationManagerBuilder并设置全局 AuthenticationManager 作为 parent。如果适配器 A 在 configure(AuthenticationManagerBuilder) 里注册了 userDetailsService适配器 B 没有注册那么 A 对应的过滤器链会使用自己构建的认证管理器B 就 fallback 到全局那个。这本来没问题但如果你同时在多个适配器里都注册了 userDetailsService并且没有指定 OrderSpring 容器会在收集 UserDetailsService 时直接报冲突。解决方案有两个层面一是全局共享把 UserDetailsService 定义成独立 Bean只在一个适配器里引用二是每个适配器配各自的用户存储但要显式区分数据源别让容器无从选择。5. 源码级排错实测过的五个坑和定位方式读源码不是为了背概念是为了踩坑时能快速定位。我把自己实际排过的五个问题整理成速查每个问题都标记了从源码哪个位置入手。5.1 权限规则不生效先检查是不是被更靠前的过滤器链“截胡”现象配了一个适配器放行 /public/**但访问 /public/a 还是跳登录。这时把断点打在 FilterChainProxy.doFilter 上看它遍历 securityFilterChains 时第一个匹配的是谁。绝大多数情况是前面还有一个 Order 更小的适配器它的 RequestMatcher 匹配所有请求把 /public/** 给吞了。定位思路打开 actuator 的 mappings 端点或者临时打日志输出所有 DefaultSecurityFilterChain 的 RequestMatcher 和过滤器数量一目了然。curl http://localhost:8080/actuator/mappings但更直接的做法是看 FilterChainProxy 的字段 securityFilterChains在调试器里展开每个链的 matcher。5.2 PasswordEncoder 报错DelegatingPasswordEncoder 的默认行为现象程序启动正常一登录就报 There is no PasswordEncoder mapped for the id null。这个报错跟 WebSecurityConfigurerAdapter 的关系在于你通过适配器的 configure(AuthenticationManagerBuilder) 配置了 userDetailsService但没给这个构建器指定 passwordEncoder。Spring Security 默认使用 DelegatingPasswordEncoder它要求数据库里的密码必须带 {id} 前缀比如 {bcrypt}xxx。如果存的是明文DelegatingPasswordEncoder 解析不到算法 id就报这个错。源码位置在 AbstractDaoAuthenticationProvider 的密码比对逻辑里。解决方式有两种密码存储时直接写入 {bcrypt} 前缀或者显式声明 PasswordEncoder BeanBean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }如果你显式声明了 PasswordEncoder Bean自动配置会把它应用到全局适配器里的 configure(AuthenticationManagerBuilder) 也可以直接用 auth.userDetailsService(...).passwordEncoder(passwordEncoder)。5.3 静态资源与 404WebSecurity.ignoring 和 permitAll 的选择现象登录后访问一个不存在的 URL居然被重定向回登录页。这其实不是 bug而是安全过滤器链的正常表现。Spring Security 默认对没有匹配到任何规则的 URL 执行 authenticated未认证就跳登录。404 页面本身也是一个请求同样会被拦截。解决时先想清楚需求。如果只是希望某些路径不鉴权用 permitAll 更安全因为请求会经过过滤器链可以继续记录安全日志或者做后续审计。如果是纯静态资源追求性能和完全旁路才考虑 web.ignoring()。5.4 两个适配器导致认证配置丢失WebSecurity 构建时的 Configurer 列表现象项目里有两个适配器其中一个适配器里配置了表单登录页和登录成功跳转但实际访问时发现另一个适配器的行为生效了。这种情况的根因是请求命中了更靠前的适配器。当你把两个适配器的匹配范围通过 antMatcher 分开后问题一般会消失。源码层面问题出在 WebSecurityConfiguration 收集全部 SecurityConfigurer 并按 Order 排序两条链各自维护自己的登录配置不存在“配置覆盖”一说只有“谁先命中”。所以排查思路不是找覆盖关系而是看请求实际命中了哪条链。可以先在每条适配器的 configure(HttpSecurity) 里加上 http.authorizeRequests().anyRequest().authenticated()然后用不同路径验证匹配结果。5.5 自定义认证逻辑被覆盖AuthenticationManager Bean 的暴露与否现象写了一个继承适配器的配置类在 configure(AuthenticationManagerBuilder) 里定义了 authenticationProvider结果自己写的 Controller 里注入 AuthenticationManager 后调 authenticate 时抛异常说不支持当前的 UsernamePasswordAuthenticationToken。原因是 Controller 里注入的 AuthenticationManager 是全局那个它不一定包含你在局部适配器里定义的 authenticationProvider。源码里 localConfigureAuthenticationBldr 构建出来的 AuthenticationManager 只在当前 HttpSecurity 的过滤器链内部共享并不会自动注册为容器 Bean。解决方式是显式暴露 BeanBean Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }这样适配器内部构建的认证管理器会被提升为全局 BeanController 里注入的就是同一个实例自定义 provider 才会生效。6. 从适配器到 Lambda DSL这套设计的演进与迁移建议6.1 适配器模式一个聪明的历史妥协看完整套源码你会承认 WebSecurityConfigurerAdapter 确实是一个聪明的设计。它把 Spring Security 复杂的构建过程包装成简单的继承方式让大多数开发者只需要关注业务规则不用理解 FilterChainProxy、SecurityBuilder、SecurityConfigurer 这些底层概念。但这种设计也有代价。继承意味着单点扩展一个类里塞太多配置时会变得臃肿。而且适配器的初始化时序是隐式的getHttp() 里的调用链很长新手很难理解为什么 configure(AuthenticationManagerBuilder) 在 configure(HttpSecurity) 之前执行。源码里那一大堆 protected 方法本质上是为了向后兼容而保留的历史包袱。6.2 SecurityFilterChain Bean 时代的新写法Spring Security 5.2 之后官方推荐不再继承适配器而是直接声明 SecurityFilterChain BeanBean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(/public/**).permitAll() .anyRequest().authenticated() .and() .formLogin(); return http.build(); }这种写法把 HttpSecurity 当成一个普通的 Bean 方法参数注入不再有适配器的生命周期负担。多个过滤器链也是同理按方法名字和 Order 排列即可。配多套规则时这样的表达方式比继承多个适配器更直白也不容易出现 AuthenticationManager 归属不清的问题。如果项目还在用 Spring Boot 2.7 之前的版本你依然可以用适配器如果是新项目强烈建议直接采用 SecurityFilterChain Bean 的方式写因为新版本源码已经把大量适配器内部的默认逻辑迁到了 HttpSecurity 的自动配置中迁移到新写法可以避免未来升级时的磕绊。6.3 迁移时的实测心得与建议我自己从适配器迁移到 SecurityFilterChain Bean 时遇到的主要坑有两个。第一原来在 configure(AuthenticationManagerBuilder) 里写的 userDetailsService 要改成独立的 Bean然后用 AuthenticationManagerBuilder 注入或者直接依赖 UserDetailsService Bean。第二原来通过 authenticationManagerBean() 暴露的 AuthenticationManager 要直接用 AuthenticationConfiguration.getAuthenticationManager() 获取这个类在自动配置中已经注册。迁移的思路其实很清晰把适配器拆成三层一是 UserDetailsService 和 PasswordEncoder 的 Bean二是 SecurityFilterChain 方法三是需要认证管理器的自定义组件单独注入。拆完之后代码可读性好了很多排查问题时也更容易定位是哪条链出了问题。如果你还在面向 Spring Security 的配置苦战我的建议是从源码出发先理解适配器三条调用链的时序再逐步过渡到新的 Lambda DSL 写法。底层原理没变变的只是表达方式。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑