Spring Security重构后调用AuthenticationManager遇StackOverflowError求助
问题分析:Spring Security认证时出现StackOverflowError
问题描述
重构代码后,调用authenticationManager.authenticate()方法触发StackOverflowError,错误栈显示AOP代理循环调用,邮箱和密码验证逻辑本身无问题。
相关代码
SpringSecurity配置类
@Configuration public class SpringSecurity { private CustomerRepository customerRepository; public SpringSecurity(CustomerRepository customerRepository) { this.customerRepository = customerRepository; } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @EventListener(ApplicationReadyEvent.class) public void saveCostumer(){ Customer customer = new Customer(1l, "test", "test", passwordEncoder().encode("test12311"), "test", "2345"); customerRepository.save(customer); } @Bean public UserDetailsService userDetailsService(){ return username -> customerRepository.findByEmail(username) .orElseThrow(() -> new UsernameNotFoundException("User email not found")); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http .authorizeRequests((auth) -> auth .antMatchers("/api/auth/login").permitAll().antMatchers("/customer/test").permitAll() .anyRequest().authenticated() ); return http.build(); } }
AuthController控制器
public class AuthController { private AuthenticationManager authenticationManager; public AuthController(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @PostMapping("/login") public ResponseEntity<String> authenticateCustomer(@RequestBody LoginDto loginDto){ Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginDto.getEmail(), loginDto.getPassword())); Customer principal = (Customer) authentication.getPrincipal(); System.out.println(principal.getUsername().toString()); return null; } }
错误信息
路径为[]的上下文中,servlet [dispatcherServlet]的Servlet.service()方法抛出异常[Handler dispatch failed; nested exception is java.lang.StackOverflowError],根本原因为:
java.lang.StackOverflowError: null at java.base/jdk.internal.misc.Unsafe.getReferenceVolatile(Native Method) ~[na:na] at java.base/jdk.internal.misc.Unsafe.getReferenceAcquire(Unsafe.java:2148) ~[na:na] at java.base/java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:760) ~[na:na] at java.base/java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:938) ~[na:na] at org.springframework.aop.framework.AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport.java:468) ~[spring-aop-5.3.23.jar:5.3.23] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:199) ~[spring-aop-5.3.23.jar:5.3.23] at jdk.proxy2/jdk.proxy2.$Proxy104.authenticate(Unknown Source) ~[na:na] ...(重复的代理调用栈)
问题原因
错误源于AuthenticationManager代理对象的循环调用:通过AuthenticationConfiguration.getAuthenticationManager()获取的实例是AOP代理,调用authenticate()时触发代理逻辑,而代理逻辑又再次调用该方法,形成无限循环,最终导致栈溢出。
解决方案
方式一:指定具体实现类类型
修改配置类中的authenticationManager Bean定义,明确返回ProviderManager(Spring Security默认的AuthenticationManager实现类),避免Spring创建多余的AOP代理:
@Bean public ProviderManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return (ProviderManager) authenticationConfiguration.getAuthenticationManager(); }
方式二:添加@Primary注解
如果方式一无效,在authenticationManager Bean上添加@Primary注解,确保Spring注入的是你定义的实例,而非其他代理对象:
@Bean @Primary public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); }
额外优化点
Customer类必须实现UserDetails接口,否则(Customer) authentication.getPrincipal()会出现类型转换错误。saveCostumer()方法中直接调用passwordEncoder()会每次创建新的BCryptPasswordEncoder实例,建议改为注入Bean:
private final PasswordEncoder passwordEncoder; public SpringSecurity(CustomerRepository customerRepository, PasswordEncoder passwordEncoder) { this.customerRepository = customerRepository; this.passwordEncoder = passwordEncoder; } @EventListener(ApplicationReadyEvent.class) public void saveCostumer(){ Customer customer = new Customer(1l, "test", "test", passwordEncoder.encode("test12311"), "test", "2345"); customerRepository.save(customer); }
内容的提问来源于stack exchange,提问作者Dev007
相关产品推荐
相关产品推荐

