如何在Spring Security中限制登录尝试?

如何在Spring Security中限制登录尝试?,security,login,spring-security,Security,Login,Spring Security,Spring Security中是否有一些配置或可用模块来限制登录尝试(理想情况下,我希望在后续失败的尝试之间有一个增加的等待时间)?如果不是,API的哪一部分应该用于此目的?实现一个AuthenticationFailureHandler,更新数据库中的计数/时间。我不会指望使用会话,因为攻击者无论如何都不会发送cookies。我最近实现了一个类似的功能,使用JMX监控登录失败。请看我对问题回答中的代码。身份验证提供者的身份验证方法的一个方面更新MBean,并与通知侦听器(问题中未显示代码)协

Spring Security中是否有一些配置或可用模块来限制登录尝试(理想情况下,我希望在后续失败的尝试之间有一个增加的等待时间)?如果不是,API的哪一部分应该用于此目的?

实现一个AuthenticationFailureHandler,更新数据库中的计数/时间。我不会指望使用会话,因为攻击者无论如何都不会发送cookies。

我最近实现了一个类似的功能,使用JMX监控登录失败。请看我对问题回答中的代码。身份验证提供者的身份验证方法的一个方面更新MBean,并与通知侦听器(问题中未显示代码)协作,阻止用户和IP,发送警报电子邮件,甚至在失败超过阈值时暂停登录

编辑

与我对问题的回答类似,我认为捕获身份验证失败事件(而不是定制处理程序)并将信息存储在数据库中也会起作用,它也会保持代码的解耦。

正如Rob Winch在中建议的那样,我只是将
DaoAuthenticationProvider子类化(这也可以使用Ritesh建议的方面)来限制失败登录的数量,但您也可以声明先决条件:

public class LimitingDaoAuthenticationProvider extends DaoAuthenticationProvider {
  @Autowired
  private UserService userService;
    @Override
    public Authentication authenticate(Authentication authentication)
        throws AuthenticationException {
      // Could assert pre-conditions here, e.g. rate-limiting
      // and throw a custom AuthenticationException if necessary

      try {
        return super.authenticate(authentication);
      } catch (BadCredentialsException e) {
        // Will throw a custom exception if too many failed logins have occurred
        userService.recordLoginFailure(authentication);
        throw e;
      }
   }
}
在Spring配置XML中,只需引用以下bean:

<beans id="authenticationProvider"   
    class="mypackage.LimitingDaoAuthenticationProvider"
    p:userDetailsService-ref="userDetailsService"
    p:passwordEncoder-ref="passwordEncoder"/>

<security:authentication-manager>
    <security:authentication-provider ref="authenticationProvider"/>
</security:authentication-manager>


请注意,我认为不应该使用依赖于访问
AuthenticationException
身份验证
外部信息
属性(例如实现
AuthenticationFailureHandler
)的解决方案,因为这些属性现在已被弃用(至少在SpringSecurity3.1中).

您还可以使用实现ApplicationListener的服务来更新数据库中的记录


请参阅spring应用程序事件。

这是我的实现,希望有帮助

  • 创建一个表来存储任何无效的登录尝试
  • 如果无效尝试>允许的最大值,请将UserDetail.accountNonLocked设置为false
  • Spring Security将为您处理“锁定过程”(请参阅
    AbstractUserDetailsAuthenticationProvider
  • 最后,扩展DaoAuthenticationProvider,并集成其中的逻辑

    @Component("authenticationProvider")
    public class YourAuthenticationProvider extends DaoAuthenticationProvider {
    
    @Autowired
    UserAttemptsDao userAttemptsDao;
    
    @Override
    public Authentication authenticate(Authentication authentication) 
          throws AuthenticationException {
    
      try {
    
        Authentication auth = super.authenticate(authentication);
    
        //if corrent password, reset the user_attempts
        userAttemptsDao.resetFailAttempts(authentication.getName());
    
        return auth;
    
      } catch (BadCredentialsException e) { 
    
        //invalid login, update user_attempts, set attempts+1 
        userAttemptsDao.updateFailAttempts(authentication.getName());
    
        throw e;
    
      } 
    
    }
    
    
    }
    
    有关完整的源代码和实现,请参阅此-,Spring 4.2以上版本的

    @Component
    public class AuthenticationEventListener {
    
        @EventListener
        public void authenticationFailed(AuthenticationFailureBadCredentialsEvent event) {
    
            String username = (String) event.getAuthentication().getPrincipal();
    
            // update the failed login count for the user
            // ...
        }
    
    }
    
  • 创建一个表来存储失败尝试的值,例如:user\u尝试
  • 编写自定义事件侦听器

     @Component("authenticationEventListner")
     public class AuthenticationEventListener
     implements AuthenticationEventPublisher
     {
     @Autowired
     UserAttemptsServices userAttemptsService;
    
     @Autowired
     UserService userService;
    
     private static final int MAX_ATTEMPTS = 3;
     static final Logger logger = LoggerFactory.getLogger(AuthenticationEventListener.class);   
    
     @Override
     public void publishAuthenticationSuccess(Authentication authentication) {          
     logger.info("User has been logged in Successfully :" +authentication.getName());       
     userAttemptsService.resetFailAttempts(authentication.getName());       
     }
    
    
     @Override
     public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {               
     logger.info("User Login failed :" +authentication.getName());      
     String username = authentication.getName().toString();
     UserAttempts userAttempt =  userAttemptsService.getUserAttempts(username);
     User userExists = userService.findBySSO(username);
    
     int attempts = 0;
     String error = "";
     String lastAttempted = "";             
     if (userAttempt == null) {     
    
        if(userExists !=null ){                     
        userAttemptsService.insertFailAttempts(username);   }       
      } else {                
          attempts = userAttempt.getAttempts();
          lastAttempted = userAttempt.getLastModified();
        userAttemptsService.updateFailAttempts(username, attempts);         
        if (attempts + 1 >= MAX_ATTEMPTS) {                 
            error = "User account is locked! <br>Username : "
                           + username+ "<br>Last Attempted on : " + lastAttempted;          
        throw new LockedException(error);           
        }                                   
      }
    throw new BadCredentialsException("Invalid User Name and Password");
    
    
    
     }
      }
    

    不幸的是,以这种方式获取主体(其用户名或id)很困难,因为AuthenticationException中的getAuthentication和getExtraInformation都已被弃用,因此您无法从数据库中获取用户(不解析HttpServletRequest的参数)。使用AuthenticationProvider似乎适用于(如下,类似于Ritesh的建议)。我发现这种方法比创建自定义身份验证提供程序的其他解决方案要干净得多。从spring 4.2开始,这可以通过注释处理,进一步将类与spring Security framework分离。无需引发BadCredentialsException-它将由事件发布者(例如ProviderManager)引发。侦听器方法的问题:您没有真正锁定用户帐户。下次用户输入有效凭据时,系统应显示错误消息“您的帐户已锁定”,但实际上它将允许成功身份验证。
             1) @Autowired
             @Qualifier("authenticationEventListner")
             AuthenticationEventListener authenticationEventListner;
    
          2) @Bean
             public AuthenticationEventPublisher authenticationListener() {
             return new AuthenticationEventListener();
             }
          3) @Autowired
             public void 
             configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
             auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
             //configuring custom user details service
             auth.authenticationProvider(authenticationProvider);
             // configuring login success and failure event listener
             auth.authenticationEventPublisher(authenticationEventListner);
             }