告别WebSecurityConfigurerAdapter:Spring Security 5.7+组件化配置实战指南

张开发
2026/4/7 6:12:32 15 分钟阅读

分享文章

告别WebSecurityConfigurerAdapter:Spring Security 5.7+组件化配置实战指南
1. 从WebSecurityConfigurerAdapter到组件化配置的转变如果你最近在升级Spring Boot应用特别是从2.x版本迁移到3.x肯定会遇到一个重大变化Spring Security 5.7版本中WebSecurityConfigurerAdapter这个老朋友已经被正式弃用了。这可不是简单的API调整而是整个安全配置理念的转变。我刚开始接触这个变化时也很困惑毕竟WebSecurityConfigurerAdapter用了这么多年突然说要换方式感觉就像老司机被要求换掉开了十年的手动挡汽车。但实际用下来发现新的组件化配置方式其实更灵活、更符合现代Spring应用的开发模式。官方给出的理由是鼓励用户转向基于组件的安全配置。简单来说就是不再需要继承那个庞大的适配器类而是通过定义各种Bean来实现安全配置。这种变化其实和Spring整体向函数式编程、组件化发展的趋势是一致的。2. 核心组件SecurityFilterChain实战2.1 基础HTTP安全配置以前我们是这样配置HTTP安全的Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic(); } }现在要改成这样Configuration public class SecurityConfig { Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz - authz .anyRequest().authenticated() ) .formLogin(withDefaults()) .httpBasic(withDefaults()); return http.build(); } }几个关键变化不再需要继承WebSecurityConfigurerAdapter配置逻辑基本保持一致但使用了lambda表达式需要显式返回构建好的SecurityFilterChain2.2 自定义请求授权规则实际项目中我们通常需要更精细的权限控制。比如只允许管理员访问/admin路径Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz - authz .requestMatchers(/admin/**).hasRole(ADMIN) .requestMatchers(/user/**).hasAnyRole(USER, ADMIN) .requestMatchers(/public/**).permitAll() .anyRequest().authenticated() ) .formLogin(withDefaults()); return http.build(); }这里有几个实用技巧使用hasRole()和hasAnyRole()进行角色检查permitAll()表示完全开放路径匹配支持Ant风格和正则表达式3. WebSecurityCustomizer的使用3.1 忽略特定请求有时候我们需要完全绕过安全链比如对静态资源或健康检查端点旧方式Override public void configure(WebSecurity web) { web.ignoring().antMatchers(/static/**, /health); }新方式Bean public WebSecurityCustomizer webSecurityCustomizer() { return web - web.ignoring().requestMatchers(/static/**, /health); }3.2 与permitAll的区别这里有个重要区别需要注意ignoring()会完全绕过安全过滤器链permitAll()仍然会经过过滤器只是允许所有访问一般来说对真正的静态资源使用ignoring()对需要记录访问的公开API使用permitAll()。4. 认证管理的现代化配置4.1 内存认证配置开发环境常用的内存用户存储配置变化如下旧方式Override protected void configure(AuthenticationManagerBuilder auth) { auth.inMemoryAuthentication() .withUser(user).password({noop}password).roles(USER) .and() .withUser(admin).password({noop}admin).roles(ADMIN); }新方式Bean public InMemoryUserDetailsManager userDetailsService() { UserDetails user User.withUsername(user) .password({noop}password) .roles(USER) .build(); UserDetails admin User.withUsername(admin) .password({noop}admin) .roles(ADMIN) .build(); return new InMemoryUserDetailsManager(user, admin); }注意生产环境绝对不要使用{noop}这样的明文密码这里只是为了示例简单。4.2 JDBC认证配置数据库认证的配置也有显著变化旧方式Override protected void configure(AuthenticationManagerBuilder auth) { auth.jdbcAuthentication() .dataSource(dataSource) .usersByUsernameQuery(select username,password,enabled from users where username?) .authoritiesByUsernameQuery(select username,authority from authorities where username?); }新方式Bean public UserDetailsManager users(DataSource dataSource) { JdbcUserDetailsManager users new JdbcUserDetailsManager(dataSource); users.setUsersByUsernameQuery(select username,password,enabled from users where username?); users.setAuthoritiesByUsernameQuery(select username,authority from authorities where username?); return users; }4.3 LDAP认证配置LDAP配置的变化更大一些旧方式Override protected void configure(AuthenticationManagerBuilder auth) { auth.ldapAuthentication() .userDnPatterns(uid{0},oupeople) .contextSource() .url(ldap://localhost:389/dcexample,dccom); }新方式Bean public LdapContextSource contextSource() { LdapContextSource contextSource new LdapContextSource(); contextSource.setUrl(ldap://localhost:389); contextSource.setBase(dcexample,dccom); return contextSource; } Bean public AuthenticationManager ldapAuthManager(LdapContextSource contextSource) { LdapBindAuthenticationManagerFactory factory new LdapBindAuthenticationManagerFactory(contextSource); factory.setUserDnPatterns(uid{0},oupeople); return factory.createAuthenticationManager(); }5. 高级配置技巧与最佳实践5.1 多SecurityFilterChain配置新架构的一个强大特性是可以定义多个安全过滤器链Bean Order(1) public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { http .securityMatcher(/api/**) .authorizeHttpRequests(authz - authz .anyRequest().hasRole(API_USER) ) .httpBasic(withDefaults()); return http.build(); } Bean Order(2) public SecurityFilterChain webFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz - authz .anyRequest().authenticated() ) .formLogin(withDefaults()); return http.build(); }5.2 自定义AuthenticationManager有时候我们需要完全控制认证过程Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz - authz .anyRequest().authenticated() ) .authenticationManager(new CustomAuthenticationManager()) .httpBasic(withDefaults()); return http.build(); }5.3 密码编码的最佳实践无论使用哪种认证方式密码安全都是重中之重Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } Bean public UserDetailsManager users(DataSource dataSource, PasswordEncoder encoder) { UserDetails user User.builder() .username(user) .password(encoder.encode(password)) .roles(USER) .build(); JdbcUserDetailsManager users new JdbcUserDetailsManager(dataSource); users.createUser(user); return users; }记住永远不要存储明文密码BCrypt是目前推荐的选择。6. 迁移过程中的常见问题在实际迁移过程中我遇到过几个典型的坑Lambda表达式不熟悉新的DSL大量使用lambda刚开始可能会觉得语法奇怪。建议先写个小demo熟悉一下。Bean循环依赖当自定义的UserDetailsService和AuthenticationManager相互引用时容易出问题。解决方法是用Lazy注解或者重构代码结构。静态资源访问被拦截忘记配置WebSecurityCustomizer导致CSS/JS加载不了。记得检查哪些路径应该被忽略。过时的API警告即使代码能运行也要注意IDE给出的过时API提示它们可能在未来版本被移除。测试代码需要同步更新如果你有用WithMockUser等注解的测试可能需要调整以适应新的安全配置方式。

更多文章