How can I limit login attempts in Spring Security?

2020-02-02 11:56发布

问题:

Is there some configuration or available module in Spring Security to limit login attempts (ideally, I'd like to have an increasing wait time between subsequent failed attempts)? If not, which part of the API should be used for this?

回答1:

Implement an AuthenticationFailureHandler that updates a count/time in the DB. I wouldn't count on using the session because the attacker is not going to be sending cookies anyway.



回答2:

From Spring 4.2 upwards annotation based event listeners are available:

@Component
public class AuthenticationEventListener {

    @EventListener
    public void authenticationFailed(AuthenticationFailureBadCredentialsEvent event) {

        String username = (String) event.getAuthentication().getPrincipal();

        // update the failed login count for the user
        // ...
    }

}


回答3:

I recently implemented a similar functionality to monitor login failures using JMX. Please see the code in my answer to question Publish JMX notifications in using Spring without NotificationPublisherAware. An aspect on the authenticate method of authentication provider updates MBean and works with a notification listener (code not shown in that question) to block user and IP, send alert emails and even suspend the login if failures exceed a threshold.

Edit
Similar to my answer to question Spring security 3 : Save informations about authentification in database, I think that capturing an authentication failure event (as opposed to customizing a handler) and storing information in database will also work and it will keep the code decoupled as well.



回答4:

As suggested by Rob Winch in http://forum.springsource.org/showthread.php?108640-Login-attempts-Spring-security, I just subclassed DaoAuthenticationProvider (which could also have been done using an aspect as Ritesh suggests) to limit the number of failed logins, but you could also assert pre-conditions as well:

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;
      }
   }
}

In Spring config XML, simply reference this 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>

Note that I think that solutions which rely on accessing an AuthenticationException's authentication or extraInformation properties (such as implementing an AuthenticationFailureHandler) should probably not be used because those properties are now deprecated (in Spring Security 3.1 at least).



回答5:

You could also use a service which implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> to update the record in DB.

See spring application events.



回答6:

Here is my implementation, hope help.

  1. Create a table to store any invalid login attempts.
  2. If invalid attempts > max allowed, set UserDetail.accountNonLocked to false
  3. Spring Security will handle the "lock process" for you. (refer to AbstractUserDetailsAuthenticationProvider)

Last, extends DaoAuthenticationProvider, and integrate the logic inside.

@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;

  } 

}


}

For full source code and implementation, please refer to this - Spring Security limit login attempts example,



回答7:

  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.Security Configuration

         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);
         }