Spring Boot实现AuthenticationManager时出现StackOverflowError问题求助
我想用Spring内置的AuthenticationManager实现登录功能,代码按预期编写,但一直收到以下错误:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed: java.lang.StackOverflowError] with root cause
我知道程序陷入了递归循环,但不清楚具体原因。
我的User类
@Entity @Table(name = "users") @Data public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int userId; @Column(nullable = false, unique = true) private String username; @Column(nullable = false, unique = true) private String email; @Column(nullable = false) private String password; @Column(nullable = false) private String street; @Column(nullable = false) private int streetNo; @Column(nullable = false) private int zipCode; @Column(nullable = false) private String city; @ManyToMany // add fetchtypes later private Set<Role> roles; }
我的LoginDTO
@Data public class LoginDTO { private String username; private String password; }
UserController中的登录方法
@PostMapping("/login") public ResponseEntity<String> authenticateUser(@RequestBody LoginDTO loginDTO) { return userService.authenticateUser(loginDTO); }
UserService中的方法(已自动装配所有所需依赖)
@Service public class UserService { private final UserRepository userRepository; private final RoleRepository roleRepository; private final PasswordEncoder passwordEncoder; private final UserMapper userMapper; private final AuthenticationManager authenticationManager; @Autowired public UserService( UserRepository userRepository, RoleRepository roleRepository, PasswordEncoder passwordEncoder, UserMapper userMapper, AuthenticationManager authenticationManager) { this.userRepository = userRepository; this.roleRepository = roleRepository; this.passwordEncoder = passwordEncoder; this.userMapper = userMapper; this.authenticationManager = authenticationManager; } public ResponseEntity<String> authenticateUser (LoginDTO loginDTO) { try { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); return new ResponseEntity<>(" is successfully logged in.", HttpStatus.OK); } catch (AuthenticationException ex) { return new ResponseEntity<>("Authentication failed: " + ex.getMessage(), HttpStatus.UNAUTHORIZED); } } }
我的SecurityConfig
我知道不应禁用CSRF,但为了先让下一步功能正常,暂时让Spring禁用了它。
@Configuration public class SecurityConfig { @Bean public static PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } @Bean SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .securityContext((context) -> context.requireExplicitSave(false)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests((requests) -> requests .requestMatchers( "/api/product/create", "/api/user/logout" ).authenticated() .requestMatchers( "/api/user/register", "/api/user/{id}", "/api/category/**", "/api/product/**", "/api/user/login" ).permitAll()); return http.build(); } }
我试过移除User类和LoginDTO的@Data注解,替换为allArgsConstructor和noArgsConstructor,但没有效果。在UserService中打印loginDTO可以正常输出,但问题似乎出在调用authenticationManager进行认证的步骤,我确认用户凭证是正确的。
问题原因与解决方案
核心原因
出现StackOverflowError是因为Spring Security找不到正确的UserDetailsService实现,导致AuthenticationManager在认证流程中陷入递归调用。默认情况下,AuthenticationManager依赖UserDetailsService加载用户信息,你的代码中缺少这个关键组件,引发循环。
解决步骤
- 实现UserDetailsService接口
创建自定义实现类,从数据库加载用户并转换为Spring Security所需的UserDetails对象:
@Service public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username)); return org.springframework.security.core.userdetails.User.builder() .username(user.getUsername()) .password(user.getPassword()) .authorities(user.getRoles().stream() .map(role -> new SimpleGrantedAuthority(role.getName())) .collect(Collectors.toList())) .build(); } }
注意:确保Role类有getName()方法,返回标准格式的角色名(如"ROLE_USER")
确认密码编码一致性
用户注册时必须使用SecurityConfig中定义的BCryptPasswordEncoder加密密码,否则认证时会因密码不匹配失败。修复Role实体的递归问题
如果Role类使用了@Data注解,添加@ToString(exclude = "users")排除与User的关联字段,避免加载角色时触发递归调用。
完成上述修改后,AuthenticationManager就能通过CustomUserDetailsService正确加载用户信息,完成认证流程,不会再出现StackOverflowError。
内容的提问来源于stack exchange,提问作者Kimberly Morgan

