Announcement Announcement Module
Collapse
No announcement yet.
Spring Security Brute Force Detection Page Title Module
Move Remove Collapse
This topic is closed
X
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring Security Brute Force Detection

    How can I detect BF attacks with Spring Security? I found this solution (http://stackoverflow.com/questions/2...-detection-bfd), but it only shows how to save these failed attempts to database. But I would like to have some kind of automated prevention, for example disable login for certain time period etc.

  • #2
    The approach suggested is pretty accurate. If you set the database field corresponding to the "locked" flag of your UserDetails, then authentication will fail until you clear it. You can potentially add a timestamp to the user table to mark the time the account was locked and use a batch process to re-enable accounts after the desired lockout period.

    Comment


    • #3
      Agree that the approach suggested there is good - just be careful with transactional consistency when you're actually going to implement it!

      Comment


      • #4
        Originally posted by Luke Taylor View Post
        The approach suggested is pretty accurate. If you set the database field corresponding to the "locked" flag of your UserDetails, then authentication will fail until you clear it. You can potentially add a timestamp to the user table to mark the time the account was locked and use a batch process to re-enable accounts after the desired lockout period.
        Ok, but how can I tell user that his account was suspended for certain time, because of too many wrong passwords?
        Last edited by __dev18; May 9th, 2010, 03:53 AM.

        Comment


        • #5
          Hi Dev,

          To beat BF attacks I composed the following basic UserDetailsService, which extends the standard InMemoryDaoImpl while keeping track of failed login attempts using an attempt count and lockout time.

          Code:
          public class LockoutDetailsService extends InMemoryDaoImpl implements ApplicationListener<ApplicationEvent> {
          	private static final transient Log logger = LogFactory.getLog(LockoutDetailsService.class);
          	private static final int DEFAULT_MAX_ATTEMPTS = 3;
          	private static final long DEFAULT_LOCKOUT_SECONDS = 60;
          
          	private Map<String, LockoutData> lockMap = new HashMap<String, LockoutData>();
          
          	private static class LockoutData {
          		int attempts;
          		long lastMillis;
          	}
          
          	@Override
          	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
          		UserDetails details = super.loadUserByUsername(username);
          		if (isLockedOut(username)) {
          			details = new User(details.getUsername(), details.getPassword(), details.isEnabled(), details
          					.isAccountNonExpired(), details.isCredentialsNonExpired(), false, details.getAuthorities());
          		}
          		return details;
          	}
          
          	private LockoutData getData(String username) {
          		LockoutData data = lockMap.get(username);
          		if (data == null) {
          			data = new LockoutData();
          			lockMap.put(username, data);
          		}
          		return data;
          	}
          
          	private boolean isLockedOut(String username) {
          		LockoutData data = getData(username);
          		if (data.attempts >= DEFAULT_MAX_ATTEMPTS) {
          			long last = System.currentTimeMillis() - data.lastMillis;
          			if (last < 1000 * DEFAULT_LOCKOUT_SECONDS) {
          				return true;
          			}
          		}
          		return false;
          	}
          
          	public void onApplicationEvent(ApplicationEvent event) {
          		if (event instanceof AuthenticationSuccessEvent) {
          			registerSuccessLogin(((AbstractAuthenticationEvent) event).getAuthentication().getName());
          		} else if (event instanceof AbstractAuthenticationFailureEvent) {
          			String username = ((AbstractAuthenticationFailureEvent) event).getAuthentication().getName();
          			String origin = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
          					.getRemoteAddr();
          			String cause = ((AbstractAuthenticationFailureEvent) event).getException().toString();
          			logger.info("Failed authentication for user '" + username + "' from ip " + origin + " caused by " + cause);
          			if (event instanceof AuthenticationFailureBadCredentialsEvent) {
          				registerFailedLogin(username, event.getTimestamp());
          			}
          		}
          	}
          
          	private void registerSuccessLogin(String username) {
          		LockoutData data = getData(username);
          		data.attempts = 0;
          	}
          
          	private void registerFailedLogin(String username, long timestamp) {
          		if (!isLockedOut(username)) {
          			LockoutData data = getData(username);
          			data.attempts++;
          			data.lastMillis = timestamp;
          		}
          	}
          }
          Overriding loadUserByUsername(..) allows it to set the 'locked' flag, while onApplicationEvent(..) keeps track of successful/failed logins. The application context looks like this:
          Code:
          <http>
          	<intercept-url pattern="/admin/**" access="ROLE_USER" />
          	<intercept-url pattern="/**" filters="none" />
          	<form-login login-page="/jsp/login.jsp" authentication-failure-url="/jsp/login.jsp?failed=true" default-target-url="/admin/index.htm" />
          	<logout />
          </http>
          
          <authentication-manager>
          	<authentication-provider user-service-ref='LockoutDetailsService' />
          </authentication-manager>
          
          <beans:bean id="LockoutDetailsService" class="com.x.LockoutDetailsService">
          	<beans:property name="userMap">
          		<beans:value>
          			user=pass,ROLE_USER
          		</beans:value>
          	</beans:property>
          </beans:bean>
          Since this class only depends on the implementation of the UserDetailsService interface, it shouldn't be too hard to convert it to extend another DAO.

          Btw: I'm still testing this code, so don't expect it to work flawlessly.

          Hope this helps.
          Cheers!
          Last edited by leo1; Aug 2nd, 2010, 04:32 AM.

          Comment

          Working...
          X