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

Spring Security 6添加AuthenticationProvider后应用启动失败求助

Spring Security 6 认证配置导致应用启动失败问题

我在Spring Boot项目中使用Spring Security 6实现认证功能,当创建AuthenticationProvider Bean并添加到filterChain后,应用无法正常启动;移除该Bean则启动正常。目前为测试已放开所有路由权限。


相关代码

SecurityConfig.java

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    @Autowired
    private UserService userDetailsService;

    @Autowired
    private BCryptPasswordEncoder encoder;

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(encoder);
        return daoAuthenticationProvider;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors((cors) -> cors.configurationSource(apiConfigurationSource()))
            .csrf(csrf -> csrf.ignoringRequestMatchers("**"))
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("**").permitAll()
                .anyRequest().authenticated()
            )
            .authenticationProvider(authenticationProvider())
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}

UserService.java

@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService{

    @Autowired
    UserRepoImpl userRepoImpl;

    final PasswordEncoder passwordEncoder;
    final JwtService jwtService;
    final AuthenticationManager authenticationManager;
    final UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder encoder;

    Integer getSessionDuration(RegisterUser user) {
        long sessionDuration = GlobalVariablesBackend.getRoleDuration(user.getRole());
        Integer intVal = Math.toIntExact(sessionDuration);
        return intVal;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
    {
        System.out.println("in user details service -> "+username);
        RegisterUser user = userRepoImpl.findByUsername(username);
        
        String jwtToken = jwtService.generateToken(user, user.getRole());
        
        AuthenticationResponse authResponse = new AuthenticationResponse();
        authResponse.setEmail(user.getEmail());
        authResponse.setRole(user.getRole());
        authResponse.setSessionDuration(getSessionDuration(user));
        authResponse.setToken(jwtToken);
        authResponse.setUsername(username);
        
        return authResponse;
    }
}

RegisterUser.java

@Entity
@Table(name = "pushcode_users")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class RegisterUser implements UserDetails{

    @Id
    @NotNull
    @Email
    @Column(unique=true)
    String email;

    @NotNull
    String password;

    @Column(name="created_date")
    @CreationTimestamp
    LocalDateTime createdDate;

    @Column(name="last_login")
    @UpdateTimestamp
    LocalDateTime lastLogin;

    @Enumerated(EnumType.STRING)
    Role role;

    @NotNull
    @Column(name="username")
    String username;

    @Transient
    String confirmPassword;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority(role.name()));
    }

    @Override
    public String getUsername() {
        return email;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

AuthenticationResponse.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationResponse extends RegisterUser{

    @JsonProperty(access = Access.WRITE_ONLY)
    private String token;
    private Role role;
    private String username;
    private String email;
    private Integer sessionDuration;
}

问题排查与修复方案

核心问题点

  1. 循环依赖:UserService实现了UserDetailsService,同时又注入了UserDetailsService自身,加上重复注入PasswordEncoder,导致Spring容器无法解析依赖。
  2. 职责混淆:loadUserByUsername是Spring Security用来加载用户认证信息的方法,你在此处生成JWT、构建响应DTO,完全偏离了该方法的核心职责,还会导致认证逻辑异常。
  3. DTO设计错误:AuthenticationResponse作为响应DTO,不应继承实体类RegisterUser,既破坏了分层设计,还会因为RegisterUser实现UserDetails导致认证逻辑混乱。

修复步骤

1. 修复UserService的循环依赖与职责

@Service
public class UserService implements UserDetailsService {

    private final UserRepoImpl userRepoImpl;

    // 只注入必要依赖,移除循环依赖项
    public UserService(UserRepoImpl userRepoImpl) {
        this.userRepoImpl = userRepoImpl;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        RegisterUser user = userRepoImpl.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在: " + username);
        }
        // 直接返回实现了UserDetails的实体类即可
        return user;
    }
}

2. 重构AuthenticationResponse为独立DTO

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationResponse {
    private String token;
    private Role role;
    private String username;
    private String email;
    private Integer sessionDuration;
}

3. 调整SecurityConfig配置

确保BCryptPasswordEncoder正确声明为Bean,简化配置逻辑:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final UserService userDetailsService;

    public SecurityConfig(UserService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(bCryptPasswordEncoder());
        return provider;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors(cors -> cors.configurationSource(apiConfigurationSource()))
                .csrf(csrf -> csrf.ignoringRequestMatchers("/**"))
                .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
                .authenticationProvider(authenticationProvider())
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }

    // 补充CORS配置实现
    private CorsConfigurationSource apiConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(List.of("*"));
        configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(List.of("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    // 声明BCryptPasswordEncoder Bean
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

4. 分离JWT生成逻辑

把Token生成、响应构建逻辑移到专门的认证服务类中:

@Service
public class AuthService {

    private final JwtService jwtService;
    private final AuthenticationManager authenticationManager;
    private final UserService userService;

    public AuthService(JwtService jwtService, AuthenticationManager authenticationManager, UserService userService) {
        this.jwtService = jwtService;
        this.authenticationManager = authenticationManager;
        this.userService = userService;
    }

    public AuthenticationResponse authenticate(String username, String password) {
        // 执行Spring Security认证流程
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(username, password)
        );
        RegisterUser user = (RegisterUser) authentication.getPrincipal();
        
        // 生成JWT Token
        String token = jwtService.generateToken(user, user.getRole());
        
        // 构建响应DTO
        AuthenticationResponse response = new AuthenticationResponse();
        response.setToken(token);
        response.setRole(user.getRole());
        response.setUsername(user.getUsername());
        response.setEmail(user.getEmail());
        response.setSessionDuration(getSessionDuration(user));
        return response;
    }

    private Integer getSessionDuration(RegisterUser user) {
        long sessionDuration = GlobalVariablesBackend.getRoleDuration(user.getRole());
        return Math.toIntExact(sessionDuration);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 08:13:23