SpringSecurity根据自定义异常返回登录错误提示信息(账户锁定)

 更新时间:2025年01月20日 09:48:23   作者:夢里花落知多少  
本文介绍了在SpringSecurity中根据自定义异常返回登录错误提示信息的方法,特别是在账户锁定时,通过记录输错次数、重写校验方法并抛出自定义异常,感兴趣的可以了解一下

一、背景

当前场景:用户在登录失败需要根据不同的场景返回不同的提示信息,例如账号不存在或密码输错提示 “用户名或密码错误”,账号禁用是提示 "账户被锁定"等,默认输错5次密码后账户会被锁定。

需求:当最后一次输错密码时需要给用户提示出 “xxx账户将被锁定” 的提示,而不是提示 “用户名或密码错误”

分析:前四次输错提示 “用户名或密码错误”,第5次输错提示 “xxx账户将被锁定”,那么需要在密码校验失败时,获取到这是第几次输错

二、实现方案

2.1 登录失败记录输错次数

@Component
public class LoginFailedListener implements ApplicationListener<AbstractAuthenticationFailureEvent> {

    @Autowired
    private CustomProperties customProperties ;

    @Autowired
    private UserDao userDao;

    @Override
    public void onApplicationEvent(AbstractAuthenticationFailureEvent abstractAuthenticationFailureEvent) {
        String loginName = abstractAuthenticationFailureEvent.getAuthentication()
            .getName();
        if (log.isInfoEnabled()) {
            log.info("登录失败 loginName=[{}]", loginName);
        }
        if (StringUtils.isBlank(loginName)) {
            return;
        }
        //查询登录账号是否存在
        UserDo condition = new UserDo();
        condition.setLoginName(loginName);
        List<UserDo> userDos = userDao.selectByRecord(condition);
        if (CollectionUtils.isEmpty(userDos)) {
            return;
        }
        UserDo userDo = userDos.get(0);
        UserDo updateDo = new UserDo();
        updateDo.setUserId(userDo.getUserId());
        Integer loginFailMaxCount = customroperties.getLoginFailMaxCount();
        if (loginFailMaxCount <= userDo.getTryTime() + 1) {
            //更新用户状态为冻结
            updateDo.setStatus(EnumUserStatus.FORBIDDEN.getCode());
            updateDo.setTryTime(loginFailMaxCount);
            if (log.isInfoEnabled()) {
                log.info("登录次数已达最大次数:{} 冻结账户 loginName=[{}]", loginFailMaxCount, loginName);
            }
        } else {
            //更新尝试次数
            updateDo.setTryTime(userDo.getTryTime() + 1);
            if (log.isInfoEnabled()) {
                log.info("尝试次数+1 loginName=[{}], tryTime=[{}]", loginName, updateDo.getTryTime());
            }
        }
        updateDo.setUpdateTime(new Date());
        userDao.updateBySelective(updateDo);
    }
}

2.2 重写校验方法,满足条件时抛出自定义异常

  • 校验抛出的异常一定要是AuthenticationException及其子类
  • 因为创建了监听器ApplicationListener的实现,源码中回调是有条件的,所以最后最好是原样抛出 BadCredentialsException ,否则ApplicationListener将不会被触发
@Slf4j
public class CustomDaoAuthenticationProvider extends DaoAuthenticationProvider {

    @Autowired
    private CustomProperties customProperties ;

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        try {
            super.additionalAuthenticationChecks(userDetails, authentication);
        } catch (AuthenticationException e) {
            if(e instanceof BadCredentialsException && userDetails instanceof UserDto){
                UserDto userDto = (UserDto) userDetails;
                //先到这里,然后去触发LoginFailedListener,达到账户被锁定这里需要+1
                //已经被冻结的不处理,只处理正常用户,并且是达到最大失败次数的那一次
                if(EnumUserStatus.NORMAL.getCode().equals(userDto.getStatus()) && Objects.equals(userDto.getTryTime() + 1, customProperties .getLoginFailMaxCount())){
                    if(log.isErrorEnabled()){
                        log.error("用户:{} 登录失败次数已达最大,账户将被锁定", userDto.getLoginName());
                    }
                    throw new BadCredentialsException(e.getMessage(), new LoginFailCountOutException("登录失败次数已达最大"));
                }else {
                    throw e;
                }

            }else {
                throw e;
            }

        }
    }


}

public class LoginFailCountOutException extends AuthenticationException {
    private static final long serialVersionUID = -8546980609242201580L;

    /**
     * Constructs an {@code AuthenticationException} with the specified message and no
     * root cause.
     *
     * @param msg the detail message
     */
    public LoginFailCountOutException(String msg) {
        super(msg);
    }

    public LoginFailCountOutException(String msg, Throwable ex) {
        super(msg, ex);
    }
}

2.3 装配自定义的AuthenticationProvider

@Bean
    public AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder, UserAuthoritiesMapper userAuthoritiesMapper) {
        DaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider();
        // 对默认的UserDetailsService进行覆盖
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder);
        authenticationProvider.setAuthoritiesMapper(userAuthoritiesMapper);
        return authenticationProvider;
    }

WebSecurityConfigurerAdapter 的配置类中注入

@Autowired
    private AuthenticationProvider authenticationProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }

2.4 AuthenticationFailureHandler 根据异常返回提示

failureHandler((request, response, ex) -> {
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                String errorMessage = this.loginFailureErrorMessage(ex);
                out.write(JSON.toJSONString(Response.<Void>builder().isSuccess(false)
                    .errorMessage(errorMessage)
                    .build()));
                out.flush();
                out.close();
            })

private String loginFailureErrorMessage(AuthenticationException ex) {
        if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
            if(ex.getCause() != null && ex.getCause() instanceof LoginFailCountOutException){
                return "登录失败次数已达最大限制, 账户冻结";
            }
            return "用户名或密码错误";
        }
        if (ex instanceof DisabledException) {
            return "账户被禁用";
        }
        if (ex instanceof LockedException) {
            return "账户被锁定";
        }
        if (ex instanceof AccountExpiredException) {
            return "账户已过期";
        }
        if (ex instanceof CredentialsExpiredException) {
            return "密码已过期";
        }
        log.warn("不明原因登录失败", ex);
        return "登录失败";
    }

到此这篇关于SpringSecurity根据自定义异常返回登录错误提示信息(账户锁定)的文章就介绍到这了,更多相关SpringSecurity自定义异常返回登录错误提示内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java byte数组转String的几种常用方法

    java byte数组转String的几种常用方法

    在Java中数组是一种非常常见的数据结构,它可以用来存储多个相同类型的数据,有时候,我们需要将数组转换为字符串,以便于输出或者传递给其他方法,这篇文章主要给大家介绍了关于java byte数组转String的几种常用方法,需要的朋友可以参考下
    2024-09-09
  • java访问者模式的静态动态及伪动态分派彻底理解

    java访问者模式的静态动态及伪动态分派彻底理解

    这篇文章主要为大家介绍了java访问者模式的静态动态及伪动态分派彻底理解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • gateway与spring-boot-starter-web冲突问题的解决

    gateway与spring-boot-starter-web冲突问题的解决

    这篇文章主要介绍了gateway与spring-boot-starter-web冲突问题的解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • SpringMVC的ModelAndView传值方法

    SpringMVC的ModelAndView传值方法

    今天小编就为大家分享一篇SpringMVC的ModelAndView传值方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • Java利用DelayQueue实现延迟任务代码实例

    Java利用DelayQueue实现延迟任务代码实例

    这篇文章主要介绍了Java利用DelayQueue实现延迟任务代码实例,DelayQueue 是一个支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储元素,同时元素必须实现 Delayed 接口,需要的朋友可以参考下
    2023-12-12
  • 应用Java泛型和反射导出CSV文件的方法

    应用Java泛型和反射导出CSV文件的方法

    这篇文章主要介绍了应用Java泛型和反射导出CSV文件的方法,通过一个自定义函数结合泛型与反射的应用实现导出CSV文件的功能,具有一定的参考借鉴价值,需要的朋友可以参考下
    2014-12-12
  • Spring发送邮件如何内嵌图片增加附件

    Spring发送邮件如何内嵌图片增加附件

    这篇文章主要介绍了Spring发送邮件如何内嵌图片增加附件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • SpringCloud读取Nacos配置中心报错及遇到的坑:Could not resolve placeholder ‘xxx’ in value ‘${xxx}

    SpringCloud读取Nacos配置中心报错及遇到的坑:Could not resolve placehold

    这篇文章主要介绍了SpringCloud读取Nacos配置中心报错:Could not resolve placeholder ‘xxx’ in value ‘${xxx},本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • SpringBoot全局异常处理机制和配置拦截器方式

    SpringBoot全局异常处理机制和配置拦截器方式

    这篇文章主要介绍了SpringBoot全局异常处理机制和配置拦截器方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • 详解Java ArrayList类

    详解Java ArrayList类

    这篇文章主要介绍了Java ArrayList类的相关资料,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07

最新评论