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

Spring Boot中@Autowired注入UserService出现空对象问题求助

问题分析与修复方案

核心问题

  1. CustomAuthenticationFilter未被Spring容器管理:你通过new CustomAuthenticationFilter(...)手动创建实例,导致类中的@Autowired UserService无法被Spring注入,始终为null。
  2. 手动实例化UserService:代码中userService = new UserService()会创建一个非Spring管理的对象,其内部的UserRepository同样无法被注入,必然引发空指针。
  3. 密码验证逻辑错误:保存用户时密码已通过BCrypt加密,但你直接用findByUsernameAndPassword查询明文密码,永远无法匹配数据库中的密文。
  4. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 05:25:18