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")); } }
问题原因及解决方案
核心原因
- Token 验证失败返回空字符串:
TokenService的validateToken方法在验证失败(如Token过期、无效)时返回空字符串,此时userRepository.findByEmail("")必然查不到用户,导致user为null。 - 未处理用户查询为空的情况:
SecurityFilter中直接使用查询结果调用user.getAuthorities(),没有判断user是否为null,触发空指针异常。 - 登录请求被过滤器逻辑干扰:即便配置了
/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
相关产品推荐
相关产品推荐

