升级Spring Security后Authentication Manager为空的最优方案咨询
重构Spring Boot 2.7.4 Spring Security配置时AuthenticationManager空指针的最佳解决方式
问题背景
在基于Spring Boot 2.7.4(使用移除了WebSecurityConfigurerAdapter的新版Spring Security)重构安全配置时,尝试通过http.getSharedObject(AuthenticationManager::class.java)获取AuthenticationManager返回null,触发空指针异常。
旧配置(基于WebSecurityConfigurerAdapter)
@Configuration @EnableWebSecurity class AppWebConfig( val customUserDetailsService: CustomUserDetailsService, val passwordEncoder: PasswordEncoder, val tokensService: TokensService ) : WebSecurityConfigurerAdapter() { @Throws(Exception::class) override fun configure(auth: AuthenticationManagerBuilder) { auth .userDetailsService(customUserDetailsService) .passwordEncoder(passwordEncoder) } @Throws(Exception::class) override fun configure(http: HttpSecurity) { http .cors() .and() .csrf().disable() .exceptionHandling() // urls permissions... .addFilter(AppAuthorizationFilter(authenticationManager(), tokensService)) } }
新配置(无WebSecurityConfigurerAdapter)
@Configuration @EnableWebSecurity class AppWebConfig( val tokensService: TokensService, ) { @Bean @Throws(Exception::class) fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager? { return authenticationConfiguration.authenticationManager } @Bean @Throws(Exception::class) protected fun filterChain(http: HttpSecurity): SecurityFilterChain { val authenticationManager = http.getSharedObject(AuthenticationManager::class.java) return http .cors() .and() .csrf().disable() // urls permissions... .addFilter(AppAuthorizationFilter(authenticationManager, tokensService)) .build() } }
未改动的AppAuthorizationFilter
class AppAuthorizationFilter( authenticationManager: AuthenticationManager, tokensService: TokensService, ) : BasicAuthenticationFilter(authenticationManager) { private val tokensService: TokensService init { this.tokensService = tokensService } @Throws(IOException::class, ServletException::class) override fun doFilterInternal( request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, ) { val header = request.getHeader(Objects.requireNonNull(HttpHeaders.AUTHORIZATION)) if (header != null) { val authorizedUser = tokensService.parseAccessToken(header) SecurityContextHolder.getContext().authentication = authorizedUser } chain.doFilter(request, response) } }
报错信息
Caused by: java.lang.NullPointerException: authenticationManager must not be null at com.app.security.config.WebConfig.filterChain(WebConfig.kt:68)
临时解决方案
通过WebApplicationContext手动获取Bean:
val authenticationManager = applicationContext.getBean("authenticationManager") as AuthenticationManager
最佳实现方式
1. 直接注入AuthenticationManager到Filter Chain方法
既然已经通过AuthenticationConfiguration定义了AuthenticationManager的Bean,直接将其作为参数注入到filterChain方法中即可,这是最符合Spring依赖注入规范的方式,避免手动查找:
@Configuration @EnableWebSecurity class AppWebConfig( val tokensService: TokensService, ) { @Bean @Throws(Exception::class) fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager { // 移除返回值的可空标记,避免空值传递 return authenticationConfiguration.authenticationManager } @Bean @Throws(Exception::class) protected fun filterChain(http: HttpSecurity, authenticationManager: AuthenticationManager): SecurityFilterChain { return http .cors() .and() .csrf().disable() // urls permissions... .addFilter(AppAuthorizationFilter(authenticationManager, tokensService)) .build() } }
2. 补充UserDetailsService与PasswordEncoder的配置
旧配置中通过AuthenticationManagerBuilder设置的用户认证逻辑,在新配置中需要通过定义AuthenticationProvider Bean来补充,确保AuthenticationManager能正常处理用户认证:
@Configuration @EnableWebSecurity class AppWebConfig( val customUserDetailsService: CustomUserDetailsService, val passwordEncoder: PasswordEncoder, val tokensService: TokensService, ) { @Bean fun authenticationProvider(): AuthenticationProvider { val daoProvider = DaoAuthenticationProvider() daoProvider.userDetailsService = customUserDetailsService daoProvider.passwordEncoder = passwordEncoder return daoProvider } @Bean @Throws(Exception::class) fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager { return authenticationConfiguration.authenticationManager } @Bean @Throws(Exception::class) protected fun filterChain(http: HttpSecurity, authenticationManager: AuthenticationManager): SecurityFilterChain { return http .cors() .and() .csrf().disable() // urls permissions... .addFilter(AppAuthorizationFilter(authenticationManager, tokensService)) .build() } }
为什么临时方案不是最优?
- 手动通过
applicationContext.getBean()属于依赖查找,不符合Spring推崇的控制反转思想,代码可读性与维护性较差; - 硬编码Bean名称
"authenticationManager"不够健壮,若后续Spring Security调整Bean命名规则,会直接导致代码失效; - 依赖查找方式增加了代码耦合度,不利于单元测试的编写。
内容的提问来源于stack exchange,提问作者asfarasiknow
相关产品推荐
相关产品推荐

