梦天城 2023-06-09 10:38 采纳率: 0%
浏览 82
已结题

security6.0循环嵌套

**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接口登录出现问题。

img

代码如下
配置类


@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, "退出成功")));
   }
}

img

下面是冲突开始的地方 ,我删掉了大部分业务代码,只保留了出错问题的代码
执行这段代码的时候出的问题,能跟跟踪到的是调用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;
    }

错误日志的最后一点是来回循环报错,太长了后面都一样的,所以没过多截取

  • 写回答

7条回答 默认 最新

  • 技术宅program 2023-06-09 10:52
    关注
    获得1.65元问题酬金

    StackOverflowError指的就是栈内存溢出

    评论

报告相同问题?

问题事件

  • 系统已结题 6月17日
  • 修改了问题 6月9日
  • 修改了问题 6月9日
  • 创建了问题 6月9日