You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 07:55:21