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; }
问题排查与修复方案
核心问题点
- 循环依赖:
UserService实现了UserDetailsService,同时又注入了UserDetailsService自身,加上重复注入PasswordEncoder,导致Spring容器无法解析依赖。 - 职责混淆:
loadUserByUsername是Spring Security用来加载用户认证信息的方法,你在此处生成JWT、构建响应DTO,完全偏离了该方法的核心职责,还会导致认证逻辑异常。 - 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
相关产品推荐
相关产品推荐

