**security6.0登录出现循环嵌套抛出异常Handler dispatch failed: java.lang.StackOverflowError
错误代码位置
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
起因:从SpringBoot2.7迁移SpringBoot3.0,同步升级security到6.0版本。
升级后因WebSecurityConfigurerAdapter弃用,重写了SecurityConfig类,现在启动正常,第三方免密登陆正常,只有login接口登录出现问题。
代码如下
配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;
/**
* 退出处理类
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
@Autowired
private AuthenticationProvider authenticationProvider;
/**
* token认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用basic明文验证
.httpBasic().disable()
// 前后端分离架构不需要csrf保护
.csrf().disable()
// 禁用默认登录页
.formLogin().disable()
// 禁用默认登出页
.logout().disable()
// 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint
.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(authenticationEntryPoint))
// 前后端分离是无状态的,不需要session了,直接禁用。
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
// 允许所有OPTIONS请求
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers(HttpMethod.POST, "/refresh_token").permitAll()
.requestMatchers("/refresh_token1").permitAll()
// 过滤请求
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.requestMatchers("/login").anonymous()
.requestMatchers("/captchaImage").anonymous()
.requestMatchers("/register").anonymous()
//aj_report 图片下载使用..
// 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”
//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")
// 允许任意请求被已登录用户访问,不检查Authority
.anyRequest().authenticated())
.authenticationProvider(authenticationProvider)
// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
.logout(lo -> lo.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler));
return http.build();
}
/**
* @return 用户详细信息 -> jwt身份验证过滤器
*/
@Bean
public UserDetailsService userDetailsService() {
return username -> userDetailsService.loadUserByUsername(username);
}
/**
* TODO 四 4.2
*
* @return 身份校验机制、身份验证提供程序
*/
@Bean
public AuthenticationProvider authenticationProvider() {
// 创建一个用户认证提供者
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
// 设置用户相信信息,可以从数据库中读取、或者缓存、或者配置文件
authProvider.setUserDetailsService(userDetailsService);
// 设置加密机制,若想要尝试对用户进行身份验证,我们需要知道使用的是什么编码
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
/**
* TODO 四 4.3提供编码机制
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* TODO 四 4.4 基于用户名和密码或使用用户名和密码进行身份验证
*
* @param config
*
* @return
*
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
/**
* 用户验证处理
*
* @author able
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private ISysUserService sysUserService;
@Autowired
private SysPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = sysUserService.selectUserByUserName(username);
if(StringUtils.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new ServiceException("登录用户:" + username + " 不存在");
} else if(UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
log.info("登录用户:{} 已被删除.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
} else if(UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
return createLoginUser(user);
}
public UserDetails createLoginUser(SysUser user) {
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}
}
```java
/**
* 自定义退出处理类 返回成功
*
* @author able
*/
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
@Autowired
private TokenService tokenService;
/**
* 退出处理
*
* @return
*/
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException
{
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser))
{
String userName = loginUser.getUsername();
// 删除用户缓存记录
tokenService.delLoginUser(loginUser.getToken());
// 记录用户退出日志
AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
}
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功")));
}
}
下面是冲突开始的地方 ,我删掉了大部分业务代码,只保留了出错问题的代码
执行这段代码的时候出的问题,能跟跟踪到的是调用authenticate出错误的。调用该方法没有进入实现类,而是进入了JdkDynamicAopProxy然后就开始循环调用抛出异常
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
@Component
public class SysLoginService {
@Autowired
private org.springframework.security.authentication.AuthenticationManager authenticationManager;
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @param code 验证码
* @param uuid 唯一标识
*
* @return 结果
*/
public String login(String username, String password, String code, String uuid) {
// 用户验证
Authentication authentication = null;
try {
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (Exception e) {
//错误次数
Object object = redisCache.getCacheObject(key);
if(null != object) {
Long increment = redisCache.increment(key);
} else {
redisCache.setCacheObject(key, 1);
}
if(e instanceof BadCredentialsException) {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
} else {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new ServiceException(e.getMessage());
}
}
return token;
}
错误日志的最后一点是来回循环报错,太长了后面都一样的,所以没过多截取
StackOverflowError指的就是栈内存溢出
看下这个是不是一直在执行形成了递归调用,日志再往下点还有吗
authenticationManager.authenticate
这个错误通常是由于应用程序中的循环嵌套过多导致的。当应用程序中的循环嵌套太多时,会导致栈溢出错误。应该是你用于验证的authenticationManager相关的代码或配置出现了问题,导致访问登录页面的时候,后台进行拦截验证的时候,路由的跳转出现了问题,导致出现了死循环出不来,一直无法完成验证。建议你如果无法直观的检查到是哪里的问题的情况下,直接调试下拦截请求那部分的代码,看下进入了哪个循环。
可能发生了无限递归,导致栈空间被耗尽而抛出
回答部分参考、引用ChatGpt以便为您提供更准确的答案:
根据您提供的代码和错误信息,问题似乎出现在登录验证部分的代码中。具体来说,问题可能是由于身份验证管理器(authenticationManager
)无法正常工作而导致的循环嵌套异常。
在代码中,您使用了org.springframework.security.authentication.AuthenticationManager
接口的实现来执行身份验证操作。然而,根据错误日志,似乎在调用authenticationManager.authenticate()
方法时,出现了循环调用的情况,并最终导致堆栈溢出异常(java.lang.StackOverflowError
)。
这种情况通常发生在AuthenticationManager
的实现类中存在循环依赖或配置问题时。可能的原因之一是,您在配置文件中未正确设置或注入AuthenticationManager
的实现类。
为了解决此问题,您可以尝试以下步骤:
AuthenticationManager
的实现类(例如,DaoAuthenticationProvider
)已正确配置并与Spring Security集成。SecurityConfig
类中的authenticationManager
bean的注入方式和配置是否正确。确保它与AuthenticationManager
的实现类正确关联。authenticationManager
正确注入到SysLoginService
中。仔细检查上述步骤,解决任何配置错误或依赖关系问题,并确保AuthenticationManager
的实现类能够正确执行身份验证操作,以避免循环嵌套异常的发生。
出现这个原因是因为循环依赖。
简单来说,A对象有一个属性是B,B对象有一个属性是A,当A对象生成的时候依赖B对象,
B对象生成的时候需要依赖A对象,这个时候就很容易形成一个闭环,如果这样一直死循环下去
问题原因:
栈内存溢出
增加下JVM堆内存大小。例如:java -Xmx512m YourMainClass。然后再测试下。
不行的话,一步一步排查,最好拿日志看下。下面是AI给出的排查方向
检查代码中是否存在无限递归调用的情况,例如递归函数、嵌套循环等。如果有,请修改代码以避免无限递归。
检查代码中是否存在死循环的情况,例如无限循环的if语句、while循环等。如果有,请修改代码以避免死循环。
检查代码中是否存在过多的线程或并发操作,这可能会导致栈溢出异常。如果是这种情况,请考虑使用线程池或其他并发控制技术来减少线程数量。
如果以上步骤都无法解决问题,您可以尝试增加JVM堆内存大小,以便为程序提供更多的内存空间。您可以通过在启动脚本中设置-Xmx参数来增加JVM堆内存大小。例如:java -Xmx512m YourMainClass。