Java API安全过滤器空指针异常:UserRepository注入为空
java.lang.NullPointerException: Cannot invoke "school.pachecos.api.users.UserRepository.findByEmail(String)" because "this.user_repository" is null
相关代码
SecurityFilter
@Component public class SecurityFilter extends OncePerRequestFilter { @Autowired TokenService token_service = new TokenService(); @Autowired private UserRepository user_repository; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filter_chain) throws ServletException, IOException { String token = getToken(request); if(token != null){ String token_subject = token_service.getTokenSubject(token); UserDetails user_entity = user_repository.findByEmail(token_subject); Authentication authentication = new UsernamePasswordAuthenticationToken(user_entity, null, user_entity.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } filter_chain.doFilter(request, response); } private String getToken(HttpServletRequest request){ String token = request.getHeader("Authorization"); if(token == null){ return null; } return token.replace("Bearer", ""); } }
SecurityConfigurations
@Configuration @EnableWebSecurity public class SecurityConfigurations { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(http_request -> http_request .requestMatchers("/auth/login").permitAll() .anyRequest().authenticated() ) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(new SecurityFilter(), UsernamePasswordAuthenticationFilter.class) .build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration auth_config) throws Exception { return auth_config.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
TokenService
@Service public class TokenService { private final String secret = "b0eMdsHE5jKDW9RsjuC5VrF8BT7P2oHubObbdBvaVbfzlXgKftxQ2WrZfX4RtU04"; private final String ISSUER = "API Pacheco's School"; public String createToken(UserEntity user){ try { Algorithm algorithm = Algorithm.HMAC256(secret); return JWT.create() .withIssuer(ISSUER) .withExpiresAt(getExpiresAt()) .withSubject(user.getUsername()) .sign(algorithm); } catch (JWTCreationException exception){ return null; } } private Instant getExpiresAt(){ return LocalDateTime.now().plusDays(1).toInstant(ZoneOffset.of("-03:00")); } public String getTokenSubject(String token){ try { Algorithm algorithm = Algorithm.HMAC256(secret); return JWT.require(algorithm) .withIssuer(ISSUER) .build() .verify(token) .getSubject(); } catch (JWTVerificationException exception){ return null; } } }
UserRepository
public interface UserRepository extends JpaRepository<UserEntity, Long> { @Query(value = "SELECT u FROM UserEntity u WHERE " + "u.name LIKE CONCAT('%', :name, '%') " + "OR u.birth_date = :date " + "OR u.email LIKE CONCAT('%', :email, '%') " + "OR cellphone LIKE CONCAT('%', :cellphone, '%') " ) Collection<UserEntity> searchUsers(String name, LocalDate date, String email, String cellphone); UserDetails findByEmail(String email); }
用户疑问:此前TokenService也出现过类似空指针问题,我通过new实例化解决了,但UserRepository无法这么做,请问该如何解决?
解决方法
核心原因
你在SecurityConfigurations里手动new SecurityFilter()创建实例,这个对象不属于Spring容器管理,导致@Autowired注解完全失效,UserRepository和TokenService都无法被注入,自然会出现空指针。
具体修正步骤
修复TokenService的注入方式
删掉SecurityFilter中TokenService的手动实例化,让Spring负责注入:@Autowired private TokenService token_service;让Spring管理SecurityFilter实例
在SecurityConfigurations中通过构造方法注入SecurityFilter,不要手动new:@Configuration @EnableWebSecurity public class SecurityConfigurations { private final SecurityFilter securityFilter; // 构造方法注入Spring容器管理的SecurityFilter实例 public SecurityConfigurations(SecurityFilter securityFilter) { this.securityFilter = securityFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(http_request -> http_request .requestMatchers("/auth/login").permitAll() .anyRequest().authenticated() ) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 使用注入的实例,而非手动new .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class) .build(); } // 其余Bean方法保持不变 @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration auth_config) throws Exception { return auth_config.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }可选:改用构造方法注入(推荐)
为了符合Spring最佳实践,把SecurityFilter的字段注入改成构造方法注入,避免字段注入的弊端:@Component public class SecurityFilter extends OncePerRequestFilter { private final TokenService tokenService; private final UserRepository userRepository; // 构造方法注入依赖 public SecurityFilter(TokenService tokenService, UserRepository userRepository) { this.tokenService = tokenService; this.userRepository = userRepository; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getToken(request); if(token != null){ String tokenSubject = tokenService.getTokenSubject(token); UserDetails userEntity = userRepository.findByEmail(tokenSubject); Authentication authentication = new UsernamePasswordAuthenticationToken(userEntity, null, userEntity.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } private String getToken(HttpServletRequest request){ String token = request.getHeader("Authorization"); if(token == null){ return null; } // 注意这里要保留空格,替换"Bearer "而非"Bearer",避免残留空格 return token.replace("Bearer ", ""); } }额外检查:Token的Subject匹配问题
注意到TokenService的createToken方法用user.getUsername()作为Subject,但SecurityFilter里用Subject去查询邮箱。如果你的UserEntity的username不是邮箱,需要改成user.getEmail(),否则会查不到用户导致后续空指针。
内容的提问来源于stack exchange,提问作者Antonio Pacheco

