当前位置 主页 > 服务器问题 > Linux/apache问题 >

    Spring Security实现多次登录失败后账户锁定功能

    栏目:Linux/apache问题 时间:2019-11-30 09:49

    在上一次写的文章中,为大家说到了如何动态的从数据库加载用户、角色、权限信息,从而实现登录验证及授权。在实际的开发过程中,我们通常会有这样的一个需求:当用户多次登录失败的时候,我们应该将账户锁定,等待一定的时间之后才能再次进行登录操作。

    一、基础知识回顾

    要实现多次登录失败账户锁定的功能,我们需要先回顾一下基础知识:

    Spring Security 不需要我们自己实现登录验证逻辑,而是将用户、角色、权限信息以实现UserDetails和UserDetailsService接口的方式告知Spring Security。具体的登录验证逻辑Spring Security 会帮助我们实现。 UserDetails接口中有一个方法叫做isAccountNonLocked()用于判断账号是否被锁定,也就是说我们应该通过该方法对应的set方法setAccountNonLocked(false)告知Spring Security该登录账户被锁定。 那么应该在哪里判断账号登录失败的次数并执行锁定机制呢?当然是我们之前文章给大家介绍的《自定义登录成功及失败结果处理》的AuthenticationFailureHandler。

    建议您先阅读本文,如果您对本文的实现过程感到迷惑,建议您再翻看本号之前的相关内容。

    二、实现多次登录失败锁定的原理

    一般来说实现这个需求,我们需要针对每一个用户记录登录失败的次数nLock和锁定账户的到期时间releaseTime。具体你是把这2个信息存储在mysql、还是文件中、还是redis中等等,完全取决于你对你所处的应用架构适用性的判断。具体的实现逻辑无非就是:

    登陆失败之后,从存储中将nLock取出来加1。 如果nLock大于登陆失败阈值(比如3次),则将nLock=0,然后设置releaseTime为当前时间加上锁定周期。通过setAccountNonLocked(false)告知Spring Security该登录账户被锁定。 如果nLock小于等于1,则将nLock再次存起来。 在一个合适的时机,将锁定状态重置为setAccountNonLocked(true)。

    这是一种非常典型的实现方式,笔者向大家介绍一款非常有用的开源软件叫做:ratelimitj。这个软件的功能主要是为API访问进行限流,也就是说可以通过制定规则限制API接口的访问频率。那恰好登录验证接口也是API的一种啊,我们正好也需要限制它在一定的时间内的访问次数。

    三、具体实现

    首先需要将ratelimitj通过maven坐标引入到我们的应用里面来。我们使用的是内存存储的版本,还有redis存储的版本,大家可以根据自己的应用情况选用。

     <dependency>
      <groupId>es.moki.ratelimitj</groupId>
      <artifactId>ratelimitj-inmemory</artifactId>
      <version>0.4.1</version>
     </dependency>

    之后通过继承SimpleUrlAuthenticationFailureHandler ,实现onAuthenticationFailure方法。该实现是针对登录失败的结果的处理,在我们之前的文章中已经讲过。

    @Component
    public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
     @Autowired
     UserDetailsManager userDetailsManager;
     //规则定义:1小时之内5次机会,就触发限流行为
     Set<RequestLimitRule> rules = 
      Collections.singleton(RequestLimitRule.of(1 * 60, TimeUnit.MINUTES,5)); 
     RequestRateLimiter limiter = new InMemorySlidingWindowRequestRateLimiter(rules);
     @Override
     public void onAuthenticationFailure(HttpServletRequest request,
         HttpServletResponse response, 
         AuthenticationException exception) 
         throws IOException, ServletException {
      String userId = //从request或request.getSession中获取登录用户名
      //计数器加1,并判断该用户是否已经到了触发了锁定规则
      boolean reachLimit = limiter.overLimitWhenIncremented(userId);
     if(reachLimit){ //如果触发了锁定规则,通过UserDetails告知Spring Security锁定账户
      user.setAccountNonLocked(false);
      userDetailsManager.updateUser(user);
      SysUser user = (SysUser) userDetailsManager.loadUserByUsername(userId);
     }
     //此处省略通过response做json或html响应
     }
    }