Spring Boot中@Autowired注入UserService出现空对象问题求助
问题分析与修复方案
核心问题
- CustomAuthenticationFilter未被Spring容器管理:你通过
new CustomAuthenticationFilter(...)手动创建实例,导致类中的@Autowired UserService无法被Spring注入,始终为null。 - 手动实例化UserService:代码中
userService = new UserService()会创建一个非Spring管理的对象,其内部的UserRepository同样无法被注入,必然引发空指针。 - 密码验证逻辑错误:保存用户时密码已通过BCrypt加密,但你直接用
findByUsernameAndPassword查询明文密码,永远无法匹配数据库中的密文。 - SecurityConfig配置冲突:当前使用内存用户认证,未对接数据库的UserService,实际登录逻辑不生效。
具体修复步骤
1. 修正UserService,实现Spring Security的UserDetailsService接口
Spring Security需要通过该接口获取用户信息,同时修复密码验证逻辑:
package com.insmart.app.service; import com.insmart.app.model.User; import com.insmart.app.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Service public class UserService implements UserDetailsService { @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; // 原有CRUD方法保持不变... @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("用户不存在"); } // 返回Spring Security所需的UserDetails对象 return org.springframework.security.core.userdetails.User.builder() .username(user.getUsername()) .password(user.getPassword()) // 转换用户组织为权限(可根据实际需求调整) .authorities(user.getOrganizations().stream() .map(org -> org.getOrganization()) .map(org -> new org.springframework.security.core.authority.SimpleGrantedAuthority(org)) .collect(Collectors.toList())) .build(); } // 密码验证方法:对比明文密码与数据库密文 public boolean validateUser(String username, String rawPassword) { User user = findByUsername(username); if (user == null) { return false; } return passwordEncoder.matches(rawPassword, user.getPassword()); } // 废弃原findByUsernameAndPassword方法,不再使用明文查询 }
2. 修改CustomAuthenticationFilter,通过构造器注入依赖
删除手动实例化代码,改用构造器注入UserService:
package com.insmart.app.filter; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.insmart.app.service.UserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collections; import java.util.Date; import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; private final UserService userService; // 构造器注入 @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { String username = request.getParameter("username"); String password = request.getParameter("password"); // 验证用户名密码是否合法 if (!userService.validateUser(username, password)) { throw new AuthenticationException("用户名或密码错误") {}; } UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password, Collections.emptyList()); log.info("attemptAuthentication {}", authenticationToken); return authenticationManager.authenticate(authenticationToken); } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { UserDetails user = (UserDetails) authResult.getPrincipal(); Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); String accessToken = JWT.create() .withSubject(user.getUsername()) .withExpiresAt(new Date(System.currentTimeMillis()+10*60*1000)) .withIssuer(request.getRequestURL().toString()) .withClaim("roles", user.getAuthorities().stream() .map(auth -> auth.getAuthority()) .collect(Collectors.toList())) .sign(algorithm); String refreshToken = JWT.create() .withSubject(user.getUsername()) .withExpiresAt(new Date(System.currentTimeMillis()+30*60*1000)) .withIssuer(request.getRequestURL().toString()) .sign(algorithm); response.setHeader("access-token", accessToken); response.setHeader("refresh-token", refreshToken); } }
3. 更新SecurityConfig,对接数据库用户认证
替换内存用户配置,注入UserService并传入CustomAuthenticationFilter:
package com.insmart.app.config; import com.insmart.app.filter.CustomAuthenticationFilter; import com.insmart.app.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private final UserService userService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 使用数据库用户认证 auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeRequests() .antMatchers("/css/**", "/index").permitAll() .antMatchers("/user/**").authenticated(); // 移除formLogin,改用自定义Filter处理登录请求 // 如果需要保留登录页面,需确保登录请求路径与Filter的默认路径一致(默认是/login) http.addFilter(new CustomAuthenticationFilter(authenticationManagerBean(), userService)); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
4. 可选:让User类实现UserDetails接口
如果不想在UserService中转换UserDetails,可以直接让User类实现该接口:
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import javax.persistence.*; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @Entity public class User implements UserDetails { // 原有字段... @OneToMany(mappedBy = "user") private List<Organization> organizations; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return organizations.stream() .map(org -> new SimpleGrantedAuthority(org.getOrganization())) .collect(Collectors.toList()); } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } // 原有getter/setter... }
关键说明
- 依赖注入规则:只有Spring容器管理的Bean才能使用
@Autowired,手动new的对象无法享受Spring的依赖注入能力,必须通过构造器或方法传入依赖。 - 密码安全:永远不要用明文密码查询数据库,必须通过
PasswordEncoder.matches()方法对比明文和密文。 - Spring Security认证流程:自定义Filter只需负责获取请求参数并触发认证,用户信息验证交给
UserDetailsService处理。
内容的提问来源于stack exchange,提问作者lavantho0508
相关产品推荐
相关产品推荐

