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

Spring Security 6.1.3中AuthenticationManager为空问题求助

Spring Security 6.1.3 + Spring Boot 3.1.3:AuthenticationManager为空导致NPE问题

问题描述

使用Spring Security 6.1.3和Spring Boot 3.1.3,尝试通过Basic Auth连接受保护服务并获取JWT令牌,但无论如何创建AuthenticationManager,它始终为空,抛出NullPointerException。

请求信息

POST http://localhost:8081/login
请求头中携带Basic Auth
JSON请求体: {
    "username": "shopping_list_username",
    "password": "shopping_list_password"
}

相关代码

ApplicationSecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableGlobalAuthentication
public class ApplicationSecurityConfig {

    @Value("${application.access.username}")
    private String username;
    @Value("${application.access.password}")
    private String password;


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf(AbstractHttpConfigurer::disable)
                .addFilter(new JwtRestAPIAuthenticationFilter(httpSecurity.getSharedObject(AuthenticationManager.class)))
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers(HttpMethod.GET, "/**").hasAnyAuthority("ADMIN")
                        .requestMatchers(HttpMethod.GET, "/login").hasAnyAuthority("ADMIN")
                        .anyRequest().authenticated()
                )
                .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .httpBasic(withDefaults())
                .formLogin(withDefaults());

        return httpSecurity.build();
    }

    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user = User
                .withUsername(username)
                .password(password)
                .authorities("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

}

JwtRestAPIAuthenticationFilter.java

public class JwtRestAPIAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;

    public JwtRestAPIAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {

        try {
            UsernameAndPasswordRequest authenticationRequest =
                    new ObjectMapper().readValue(request.getInputStream(), UsernameAndPasswordRequest.class);
            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    authenticationRequest.getUsername(),
                    authenticationRequest.getPassword()
            );

            return authenticationManager.authenticate(authentication);

        } catch(IOException e){
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        
        String token = Jwts.builder()
                .setSubject(authResult.getName())
                .setIssuedAt(new Date())
                .setExpiration(java.sql.Date.valueOf(LocalDate.now().plusWeeks(2)))
                .signWith(Keys.hmacShaKeyFor(Utils.SECURE_KEY.getBytes()))
                .compact();
        response.addHeader("Authorization", "Bearer " + token);
    }
}

报错信息

java.lang.NullPointerException: Cannot invoke "org.springframework.security.authentication.AuthenticationManager.authenticate(org.springframework.security.core.Authentication)" because the return value of "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.getAuthenticationManager()" is null
    at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:85) 

已尝试的解决方法

  • 通过Bean创建AuthenticationManager:
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
    
  • 从HttpSecurity对象中获取:
    http.getSharedObject(AuthenticationManager.class)
    
  • 参考Spring官方迁移文章调整代码,但仍无法解决问题。

解决方案

1. 修复自定义Filter的AuthenticationManager注入(直接解决NPE)

你的自定义Filter继承了UsernamePasswordAuthenticationFilter,父类自身维护了AuthenticationManager字段,但你在构造函数中仅给自己定义的字段赋值,没有给父类的字段赋值。父类的attemptAuthentication方法调用的是自身的getAuthenticationManager(),所以返回null导致报错。

修改Filter的构造函数,调用父类的构造函数传递AuthenticationManager:

public class JwtRestAPIAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public JwtRestAPIAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager); // 调用父类构造函数,自动赋值父类的authenticationManager字段
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        try {
            UsernameAndPasswordRequest authenticationRequest =
                    new ObjectMapper().readValue(request.getInputStream(), UsernameAndPasswordRequest.class);
            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    authenticationRequest.getUsername(),
                    authenticationRequest.getPassword()
            );
            // 直接使用父类已初始化的AuthenticationManager
            return getAuthenticationManager().authenticate(authentication);
        } catch(IOException e){
            throw new RuntimeException(e);
        }
    }

    // 其他方法不变
}

2. 正确注入AuthenticationManager到SecurityFilterChain

构建SecurityFilterChain时,不要从httpSecurity.getSharedObject()获取AuthenticationManager(此时还未初始化),直接注入你定义的AuthenticationManager Bean:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, AuthenticationManager authenticationManager) throws Exception {
    httpSecurity
            .csrf(AbstractHttpConfigurer::disable)
            // 注入AuthenticationManager并设置Filter处理路径为/login
            .addFilter(new JwtRestAPIAuthenticationFilter(authenticationManager).setFilterProcessesUrl("/login"))
            .authorizeHttpRequests((authorize) -> authorize
                    // 允许POST /login匿名访问,否则无法进入登录流程
                    .requestMatchers(HttpMethod.POST, "/login").permitAll()
                    .requestMatchers(HttpMethod.GET, "/**").hasAnyAuthority("ADMIN")
                    .anyRequest().authenticated()
            )
            .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            // 移除默认的httpBasic和formLogin,避免与自定义Filter冲突
            .httpBasic(AbstractHttpConfigurer::disable)
            .formLogin(AbstractHttpConfigurer::disable);

    return httpSecurity.build();
}

3. 添加PasswordEncoder并加密密码

Spring Security 6默认要求密码必须经过编码,否则会导致认证失败。添加PasswordEncoder Bean并加密用户密码:

// 在ApplicationSecurityConfig中添加密码编码器Bean
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

// 修改userDetailsService方法,注入PasswordEncoder并加密明文密码
@Bean
public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
    UserDetails user = User
            .withUsername(username)
            .password(passwordEncoder.encode(password))
            .authorities("ADMIN")
            .build();
    return new InMemoryUserDetailsManager(user);
}

4. 移除废弃注解

@EnableGlobalAuthentication在Spring Security 6中已废弃,建议从ApplicationSecurityConfig中移除该注解,避免潜在问题。

验证修改后的流程

  1. 发送POST /login请求,携带JSON格式的用户名密码(无需Basic Auth,自定义Filter会直接解析请求体)
  2. 自定义Filter完成认证后,返回JWT令牌在Authorization响应头中
  3. 后续请求携带Bearer令牌即可访问受保护的GET接口

内容的提问来源于stack exchange,提问作者CJJ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 08:53:10