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

23

在Spring Security中,是否有一些配置或可用模块来限制登录尝试次数(理想情况下,我希望在连续失败的尝试之间有逐渐增加的等待时间)?如果没有,应使用API的哪个部分进行此操作?

7个回答

24

从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
        // ...
    }

}

18

实现一个AuthenticationFailureHandler,它在数据库中更新计数/时间。我不会依赖于使用会话,因为攻击者不会发送cookie。


4
很遗憾,通过这种方式很难获取Principal(用于用户名或ID),因为getAuthentication和getExtraInformation都已从AuthenticationException中弃用,因此您无法从数据库中获取用户(而不解析HttpServletRequest的参数)。使用AuthenticationProvider似乎对我有用(下面是类似于Ritesh建议的内容)。 - paulcm

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

编辑
与我对“Spring Security 3:将有关认证的信息保存到数据库中”问题的回答类似,我认为捕获身份验证失败事件(而不是自定义处理程序)并将信息存储在数据库中也可以解决问题,且代码保持解耦。


6

正如Rob Winch在http://forum.springsource.org/showthread.php?108640-Login-attempts-Spring-security中所建议的那样,我只是通过子类化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>

请注意,我认为依赖访问AuthenticationExceptionauthenticationextraInformation属性的解决方案(例如实现AuthenticationFailureHandler)可能不应该使用,因为这些属性现在已被弃用(至少在Spring Security 3.1中)。

5
您还可以使用实现ApplicationListener<AuthenticationFailureBadCredentialsEvent>的服务来更新DB中的记录。
请参阅Spring应用程序事件。

2
我发现这种方式比创建自定义身份验证提供程序的其他解决方案更加简洁。从Spring 4.2开始,可以使用注释来处理此问题,进一步将您的类与Spring Security框架解耦。 - Jberg

4

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

  1. 创建一个表来存储任何无效的登录尝试。
  2. 如果无效的尝试次数超过最大允许值,则将UserDetail.accountNonLocked设置为false。
  3. 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 Security登录尝试次数限制示例


1
  1. create a table to store the values of failed attempts ex : user_attempts
  2. Write custom event listener

     @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");
    
    
    
     }
      }
    
3.安全配置
         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);
         }

不需要抛出BadCredentialsException - 它将由事件发布者(例如ProviderManager)抛出。 - isobretatel
2
使用监听器方法的问题在于:您实际上并没有锁定用户帐户。下一次用户输入有效凭据时,系统应该显示错误消息“您的帐户已被锁定”,但实际上它将允许成功验证。 - isobretatel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接