使用authenticationManager.authenticate()时出现StackOverflowError的解决方法
解决AuthenticationManager.authenticate()抛出StackOverflowError的问题
问题场景
调用authenticationManager.authenticate()方法时触发StackOverflowError,未继承已废弃的WebSecurityConfigurerAdapter,当前SecurityConfig配置如下:
@Configuration @EnableWebSecurity public class SecurityConfig{ @Bean public UserDetailsService userDetailsService() { return new CustomUserDetailsService(); } @Bean @Order(1) public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.httpBasic().disable().csrf().disable().sessionManagement() .and().authorizeRequests() .antMatchers("/**").permitAll() .anyRequest().authenticated().and().csrf().disable(); http .logout() .invalidateHttpSession(true) .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)); return http.build(); } @Bean @Order(0) public SecurityFilterChain resources(HttpSecurity http) throws Exception { http.requestMatchers((matchers) -> matchers.antMatchers("*.bundle.*")) .authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll()) .requestCache().disable() .securityContext().disable() .sessionManagement().disable(); return http.build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public PasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } }
错误日志
2022-07-18 17:26:52.277 ERROR 12368 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause java.lang.StackOverflowError: null at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.21.jar:5.3.21] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208) ~[spring-aop-5.3.21.jar:5.3.21] at com.sun.proxy.$Proxy111.authenticate(Unknown Source) ~[na:na] at jdk.internal.reflect.GeneratedMethodAccessor60.invoke(Unknown Source) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na] at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.21.jar:5.3.21] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208) ~[spring-aop-5.3.21.jar:5.3.21] at com.sun.proxy.$Proxy111.authenticate(Unknown Source) ~[na:na] at jdk.internal.reflect.GeneratedMethodAccessor60.invoke(Unknown Source) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na] at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.21.jar:5.3.21] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208) ~[spring-aop-5.3.21.jar:5.3.21] at com.sun.proxy.$Proxy111.authenticate(Unknown Source) ~[na:na] at jdk.internal.reflect.GeneratedMethodAccessor60.invoke(Unknown Source) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na] *continues till stack overflow*
认证Controller代码
@CrossOrigin @RestController public class UserController { @Autowired AuthenticationManager authenticationManager; @Autowired CustomUserService userService; @Autowired JwtTokenProvider jwtTokenProvider; @PostMapping("/login") public ResponseEntity<Map<Object, Object>> login(@RequestBody CustomUserLoginDto userDto) { try { String email = userDto.getEmail(); Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(email, userDto.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); String token = jwtTokenProvider.createToken(email); Map<Object, Object> model = new HashMap<>(); model.put("username", email); model.put("token", token); return ResponseEntity.ok(model); } catch (AuthenticationException e) { throw new BadCredentialsException("Invalid email/password supplied"); } } @PostMapping("/register") public CustomUser register(@RequestBody CustomUserCreateDto userDto) { return userService.saveUser(userDto); } }
解决方案
这个问题的核心是手动定义的AuthenticationManager Bean和Spring Security内部的AuthenticationManager形成了循环代理,导致调用authenticate时无限递归触发栈溢出。
修复步骤
移除手动定义的AuthenticationManager Bean
删掉SecurityConfig中以下代码:@Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); }在SecurityFilterChain中配置认证逻辑
通过HttpSecurity的getSharedObject获取AuthenticationManagerBuilder,绑定UserDetailsService和PasswordEncoder,让Spring自动生成正确的AuthenticationManager实例:
修改后的SecurityConfig:@Configuration @EnableWebSecurity public class SecurityConfig{ @Bean public UserDetailsService userDetailsService() { return new CustomUserDetailsService(); } @Bean @Order(1) public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 绑定用户认证逻辑 AuthenticationManagerBuilder auth = http.getSharedObject(AuthenticationManagerBuilder.class); auth.userDetailsService(userDetailsService()) .passwordEncoder(bCryptPasswordEncoder()); http.httpBasic().disable() .csrf().disable() .sessionManagement() .and() .authorizeRequests() .antMatchers("/**").permitAll() .anyRequest().authenticated() .and() .logout() .invalidateHttpSession(true) .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)); return http.build(); } @Bean @Order(0) public SecurityFilterChain resources(HttpSecurity http) throws Exception { http.requestMatchers((matchers) -> matchers.antMatchers("*.bundle.*")) .authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll()) .requestCache().disable() .securityContext().disable() .sessionManagement().disable(); return http.build(); } @Bean public PasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } }Controller中直接注入AuthenticationManager
不需要额外配置,Spring会自动将正确的AuthenticationManager实例注入到Controller中,原Controller代码无需修改。
这样就能解决循环代理导致的StackOverflowError问题,让authenticate方法正常执行用户认证逻辑。
内容的提问来源于stack exchange,提问作者kagire
相关产品推荐
相关产品推荐

