升级 Spring Boot 2.7.0 中已弃用的 WebSecurityConfigurerAdapter
问题描述
从 Spring Boot 2.7.0 开始,WebSecurityConfigurerAdapter
类已被标记为弃用,开发者需要寻找替代方案来配置 Spring Security。当你尝试将原有的基于 WebSecurityConfigurerAdapter
的安全配置迁移到新的方式时,可能会遇到各种错误,其中最常见的是关于 AuthenticationManager 配置的问题。
典型的错误信息如下:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration':
Unsatisfied dependency expressed through method 'setFilterChains' parameter 0;
...
nested exception is java.lang.IllegalStateException:
Cannot apply org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer@3fdc705c to already built object
解决方案
1. 基本配置迁移
以下是传统的基于 WebSecurityConfigurerAdapter
的配置和新的基于组件的配置方式的对比:
传统配置(已弃用)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthEntryPointJwt unauthorizedHandler;
@Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/test/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
新配置方式(推荐)
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthEntryPointJwt unauthorizedHandler;
@Autowired
private AuthTokenFilter authenticationJwtTokenFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.exceptionHandling(exp -> exp.authenticationEntryPoint(unauthorizedHandler))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/test/**").permitAll()
.anyRequest().authenticated());
http.addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
重要变化
- 不再使用
@EnableGlobalMethodSecurity
,改用@EnableMethodSecurity
antMatchers()
方法已弃用,改用requestMatchers()
AuthenticationManagerBuilder
的配置方式发生了变化configure(HttpSecurity http)
方法被filterChain(HttpSecurity http)
方法取代
2. 完整的 JWT 安全配置示例
下面是一个完整的 JWT 身份验证安全配置示例:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final AuthEntryPointJwt unauthorizedHandler;
private final AuthTokenFilter authenticationJwtTokenFilter;
public SecurityConfig(UserDetailsService userDetailsService,
AuthEntryPointJwt unauthorizedHandler,
AuthTokenFilter authenticationJwtTokenFilter) {
this.userDetailsService = userDetailsService;
this.unauthorizedHandler = unauthorizedHandler;
this.authenticationJwtTokenFilter = authenticationJwtTokenFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling(exp -> exp.authenticationEntryPoint(unauthorizedHandler))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/test/**").permitAll()
.anyRequest().authenticated());
http.addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*");
}
};
}
}
3. 基本身份验证配置示例
如果你使用的是基本身份验证而非 JWT,可以参考以下配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.requestMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web -> web.ignoring().requestMatchers("/images/**", "/js/**", "/webjars/**"));
}
}
常见问题与解决方案
1. AuthenticationManager 配置问题
错误示例
不要尝试手动构建 AuthenticationManagerBuilder:
// 不推荐的写法
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userDetailsService);
authenticationManager = authenticationManagerBuilder.build();
正确做法
使用 Spring 提供的 AuthenticationConfiguration:
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
2. UserDetailsService 配置
确保你的 UserDetailsService 正确配置:
@Service
public class CustomUserDetailService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
return User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(convertAuthorities(user.getRoles()))
.build();
}
private Set<GrantedAuthority> convertAuthorities(Set<Role> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toSet());
}
}
3. Spring Security 6.x 的额外变化
如果你使用的是 Spring Boot 3.x 或 Spring Security 6.x,还需要注意以下变化:
antMatchers()
完全被requestMatchers()
取代- Lambda DSL 成为推荐配置方式
- 一些类包路径发生了变化(从 javax 迁移到 jakarta)
总结
迁移已弃用的 WebSecurityConfigurerAdapter
主要涉及以下几个关键变化:
- AuthenticationManager 配置:使用
AuthenticationConfiguration
而非AuthenticationManagerBuilder
- HTTP 安全配置:使用
SecurityFilterChain
Bean 而非重写configure
方法 - 方法安全注解:使用
@EnableMethodSecurity
替代@EnableGlobalMethodSecurity
- 请求匹配:使用
requestMatchers()
替代antMatchers()
遵循这些变化,你可以顺利地将你的 Spring Security 配置迁移到新的方式,避免弃用警告并确保应用程序的长期可维护性。