1

I have a Struts2/Spring application and I would like to know how to block "grabbers".

On my server, I have detect that some grabber calls multiple times the same action struts and the result is that my server come too slow during the grabbing process and the database have multiple hits.

How could I stop multiple calls to the same action struts and minimize database hits ?

For example, grabber calls more than 40 times/minute the same action.


For a security point of view I think I should use a cache to store ip addresses and call number and block the ip if they exceed a limit.

But I don't know how to do this.

If you have already done the same please could you tell me how to implement a solution ?

Aleksandr M
  • 24,264
  • 12
  • 69
  • 143
  • This you can do.Using interceptor in struts2 everytime response come from same ip then redirect to warning page. – udaybhaskar Jun 15 '16 at 07:18
  • Read this: http://stackoverflow.com/questions/30726627/restriction-on-number-of-opened-tabs/30727762#30727762 .Then implement an interceptor of your, checking the IP or whatever, and filter the unwanted connections. Note that probably you could do something also at web-server level, try asking about it to your admin, if you have one – Andrea Ligios Jun 15 '16 at 07:41
  • Is it possible to implement a solution with caching like Guava or Ehcache and define an expiration for each entry in the cache ? And how can I check the number of call in 10 minutes for example ? Thank you – Snowball RC Jun 15 '16 at 12:10
  • Having captcha is the easiest solution. Disable other panels when captcha is on screen. – prem30488 Jun 15 '16 at 12:17
  • @ParthTrivedi do you an easy way to implement a captcha with Struts2 when an action struts is called too many times in 10 minutes for example ? Thank you – Snowball RC Jun 15 '16 at 12:19
  • Try Google's api for captcha named `recaptcha` – prem30488 Jun 15 '16 at 12:21
  • @ParthTrivedi in my application, I would like to protect some url that do not contains html form. Its action return json data. I would like to show a captcha for example when an IP adress call the action more than 40 times in 10 minutes for example but I don't know how to do this – Snowball RC Jun 15 '16 at 12:25
  • Try implementing interceptor that parses HTTPRequest header and get Remote address and count it. If it is greater than Threshold value intercept it. Or show him proper message on screen or timer. – prem30488 Jun 15 '16 at 12:29
  • @ParthTrivedi to count it I should have to store ip and url or action name in cache or other ? Could you give me an example to implement this solution ? – Snowball RC Jun 15 '16 at 12:32
  • There are either too many possible answers, or good answers would be too long for this format. Please add details to narrow the answer set or to isolate an issue that can be answered in a few paragraphs. – Roman C Jun 16 '16 at 08:47

1 Answers1

0

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.

prem30488
  • 2,828
  • 2
  • 25
  • 57
  • Thank you for this sample of code it is very usefull. But could you give me sample to do that with Guavas auto expire cache ? I am very interrest of this point. – Snowball RC Jun 22 '16 at 06:38
  • ImmutableSortedSet set = ContiguousSet.create(Range.open(1, 5), DiscreteDomain.integers()); // set contains [2, 3, 4] ContiguousSet.create(Range.greaterThan(0), DiscreteDomain.integers()); // set contains [1, 2, ..., Integer.MAX_VALUE] – prem30488 Jun 22 '16 at 06:50
  • Here it is a global expiration time for the cache no ? it is possible to have a ttl for each entry ? I am trying to do that with ehcache but my element in the cache never expire. And with your struts2 sample where can I use it ? and what is the 'MY_LISTENER' ? – Snowball RC Jun 22 '16 at 06:55
  • For ttl `expireAfterWrite(10, TimeUnit.MINUTES)`. But are you using ehcache? – prem30488 Jun 22 '16 at 07:32
  • You may specify a removal listener for your cache to perform some operation when an entry is removed, via `CacheBuilder.removalListener(RemovalListener)`. The `RemovalListener` gets passed a `RemovalNotification`, which specifies the `RemovalCause`, key, and value. For Logging Purpose of exception there is example on that page. – prem30488 Jun 22 '16 at 07:34
  • Yes actually I use ehcache because it's already in my webapp. – Snowball RC Jun 22 '16 at 09:14
  • In ehcache I can't use the TTL option to set expiration date of login attemps because of the update of an Element in the cache re set the TTL. If anyone knows an option to update an element in ehcache without changing the previous TTL it will be very usefull for me. Actually I make a diff with the creation date and the date when a new attemps comes. – Snowball RC Jun 24 '16 at 11:14