If you are using struts2 and spring together you should check with Spring Security's feature of limiting users with attempts. If user attempt fails 3 times the user should be blocked and do not have access to page and if attempts are less than 3 we should reset the counter. Also csrf token should be used for every login attempt differently.
Spring Security
Have a look on this implementation.
Main Files is LimitLoginAuthenticationProvider.java
package com.mkyong.web.handler;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import com.mkyong.users.dao.UserDetailsDao;
import com.mkyong.users.model.UserAttempts;
@Component("authenticationProvider")
public class LimitLoginAuthenticationProvider extends DaoAuthenticationProvider {
@Autowired
UserDetailsDao userDetailsDao;
@Autowired
@Qualifier("userDetailsService")
@Override
public void setUserDetailsService(UserDetailsService userDetailsService) {
super.setUserDetailsService(userDetailsService);
}
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
try {
Authentication auth = super.authenticate(authentication);
//if reach here, means login success, else an exception will be thrown
//reset the user_attempts
userDetailsDao.resetFailAttempts(authentication.getName());
return auth;
} catch (BadCredentialsException e) {
//invalid login, update to user_attempts
userDetailsDao.updateFailAttempts(authentication.getName());
throw e;
} catch (LockedException e){
//this user is locked!
String error = "";
UserAttempts userAttempts =
userDetailsDao.getUserAttempts(authentication.getName());
if(userAttempts!=null){
Date lastAttempts = userAttempts.getLastModified();
error = "User account is locked! <br><br>Username : "
+ authentication.getName() + "<br>Last Attempts : " + lastAttempts;
}else{
error = e.getMessage();
}
throw new LockedException(error);
}
}
}
Struts2
The same can be done by implementing interceptor in struts2.
public class MyAction implements SessionAware {
private Map<String, Object> session;
@Override
public String execute() {
if (session.containsKey("loginAttempts")) {
Integer loginAttempts = (Integer) session.get("loginAttempts");
if (loginAttempts > 3) {
//block user
} else {
session.put("loginAttempts", loginAttempts+1);
}
}
}
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
}
same using interceptor
public String intercept (ActionInvocation invocation) throws Exception {
// Get the action context from the invocation so we can access the
// HttpServletRequest and HttpSession objects.
final ActionContext context = invocation.getInvocationContext ();
HttpServletRequest request = (HttpServletRequest) context.get(HTTP_REQUEST);
HttpSession session = request.getSession (true);
// Is there a "user" object stored in the user's HttpSession?
Object user = session.getAttribute (USER_HANDLE);
if (user == null) {
// The user has not logged in yet.
// Is the user attempting to log in right now?
String loginAttempt = request.getParameter (LOGIN_ATTEMPT);
if (! StringUtils.isBlank (loginAttempt) ) { // The user is attempting to log in.
// Process the user's login attempt.
if (processLoginAttempt (request, session) ) {
// The login succeeded send them the login-success page.
return "login-success";
} else {
// The login failed. Set an error if we can on the action.
Object action = invocation.getAction ();
if (action instanceof ValidationAware) {
((ValidationAware) action).addActionError ("Username or password incorrect.");
}
}
}
// Either the login attempt failed or the user hasn't tried to login yet,
// and we need to send the login form.
return "login";
} else {
return invocation.invoke ();
}
}
You can also use Recaptcha after 3 failed attempts or reset the password.
For a security point of view you have to do a little bit more. For example use a cache to store ip addresses and login attempts and block the ip if they used up all the login attempts. With Spring and Guavas auto expire cache it's easy to implement using expireAfterWrite(10, TimeUnit.MINUTES)
.
If you want to store/cache only ipaddress and count as key value pair Spring Radis is also good alternative in spring framework.