**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;
}
错误日志的最后一点是来回循环报错,太长了后面都一样的,所以没过多截取