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

Spring Security JWT验证后user为null的原因排查

Spring Security + JWT 认证:登录时 User 为 Null 的 NullPointerException 问题

报错信息:

java.lang.NullPointerException: Cannot invoke "org.springframework.security.core.userdetails.UserDetails.getAuthorities()" because "user" is null
    at br.com.gustavo.numa.infra.security.SecurityFilter.doFilterInternal(SecurityFilter.java:33) ~[classes/:na]

问题:验证 JWT 令牌后,user 为何会变为 null?

实现代码

SecurityConfiguration

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Autowired
    SecurityFilter securityFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{
        return httpSecurity
                .csrf(csrf -> csrf.disable())
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(HttpMethod.POST, "/auth/login").permitAll()
                        .requestMatchers(HttpMethod.POST, "/auth/register").permitAll()
                        .requestMatchers(HttpMethod.GET, "/test").hasRole("USER")
                        .anyRequest().authenticated()
                )
                .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

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

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

SecurityFilter

@Component
public class SecurityFilter extends OncePerRequestFilter {

    @Autowired
    TokenService tokenService;

    @Autowired
    UserRepository userRepository;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        var token = this.recoverToken(request);
        if (token != null) {
            var login = tokenService.validateToken(token);
            UserDetails user = userRepository.findByEmail(login);

            var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }

    private String recoverToken(HttpServletRequest request) {
        var authHeader = request.getHeader("Authorization");
        if (authHeader == null) return null;
        return authHeader.replace("Bearer ", "");
    }
}

TokenService

@Component
public class TokenService {

    @Value("${api.security.token.secret}")
    private String secret;

    public String generateToken(User user) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);

            return JWT.create()
                 .withIssuer("auth-api")
                 .withSubject(user.getEmail())
                 .withExpiresAt(genExpirationDate())
                 .sign(algorithm);
        } catch (JWTCreationException exception) {
            throw new RuntimeException("Error while generating token");
        }
    }

    public String validateToken(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            return JWT.require(algorithm)
                    .withIssuer("auth-api")
                    .build()
                    .verify(token)
                    .getSubject();
        } catch (JWTVerificationException exception) {
            return "";
        }
    }


    private Instant genExpirationDate() {
        return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
    }
}

问题原因及解决方案

核心原因

  1. Token 验证失败返回空字符串:TokenService的validateToken方法在验证失败(如Token过期、无效)时返回空字符串,此时userRepository.findByEmail("")必然查不到用户,导致user为null。
  2. 未处理用户查询为空的情况:SecurityFilter中直接使用查询结果调用user.getAuthorities(),没有判断user是否为null,触发空指针异常。
  3. 登录请求被过滤器逻辑干扰:即便配置了/auth/login允许匿名访问,若请求中携带了无效Token,过滤器仍会执行验证逻辑,进而触发报错。

修复方案

1. 修正 TokenService 的 validateToken 方法

验证失败时返回null而非空字符串,明确区分验证成功/失败状态:

public String validateToken(String token) {
    try {
        Algorithm algorithm = Algorithm.HMAC256(secret);
        return JWT.require(algorithm)
                .withIssuer("auth-api")
                .build()
                .verify(token)
                .getSubject();
    } catch (JWTVerificationException exception) {
        return null; // 改为返回null
    }
}

2. 在 SecurityFilter 中增加 null 判断

逐层校验,确保只有存在有效用户时才构建Authentication:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    var token = this.recoverToken(request);
    if (token != null) {
        var login = tokenService.validateToken(token);
        // 先判断login是否有效,再查询用户
        if (login != null) {
            UserDetails user = userRepository.findByEmail(login);
            // 确保用户存在再设置认证信息
            if (user != null) {
                var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
    }
    filterChain.doFilter(request, response);
}

3. 可选:跳过无需Token验证的请求

让过滤器直接跳过登录、注册接口,减少不必要的逻辑执行:

@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
    String path = request.getRequestURI();
    return path.equals("/auth/login") || path.equals("/auth/register");
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 13:04:57