Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to delete session data at a regular time interval, every hour?

I'm generating random tokens for the reason as mentioned in this question which are placed in a java.util.List and this List is kept in a session scope.

After going through some Google search, I have decided to remove all elements (tokens) every hour contained by this List in session.

I could think of using the Quartz API but doing so, doesn't seem possible for manipulating a user's session. What I have tried with the Quartz API (1.8.6, 2.x is incompatible with Spring 3.2 which I'm using) in Spring can be seen below.

package quartz;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public final class RemoveTokens extends QuartzJobBean
{    
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException
    {
        System.out.println("QuartzJobBean executed.");
    }
}

And it was configured in the application-context.xml file as follows.

<bean name="removeTokens" class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass" value="quartz.RemoveTokens" />
    <property name="jobDataAsMap">
        <map>
            <entry key="timeout" value="5" />
        </map>
    </property>
</bean>

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
      <property name="jobDetail" ref="removeTokens"/>
      <property name="startDelay" value="10000"/>
      <property name="repeatInterval" value="3600000"/>
</bean>

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="triggers">
      <list>
          <ref bean="simpleTrigger" />
      </list>
  </property>
</bean>

The overridden method in the RemoveTokens class is executed every hour with the initial interval of 10 seconds as configured in XML but it is not possible to execute some method of some class to remove the tokens available in the List which is stored in a user's session. Is it possible?

What is the fair way to remove this List stored in a session scope with a defined time interval (every hour)? It would be far better, if it were to be made possible by using this Quartz API.


EDIT:

According to the answer below, I have tried the following but unfortunately, it didn't make a difference.

In the application-context.xml file,

<task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>
<task:executor id="taskExecutor" pool-size="5"/>
<task:scheduler id="taskScheduler" pool-size="10"/>

This requires the following additional namespaces,

xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/task
                    http://www.springframework.org/schema/task/spring-task-3.2.xsd"

The following bean was registered as a session scoped bean.

package token;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.apache.commons.lang.StringUtils;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
//@Scope("session")
public final class SessionToken implements SessionTokenService
{
    private List<String> tokens;

    private static String nextToken()
    {
        long seed = System.currentTimeMillis(); 
        Random r = new Random();
        r.setSeed(seed);
        return Long.toString(seed) + Long.toString(Math.abs(r.nextLong()));
    }

    @Override
    public boolean isTokenValid(String token)
    {        
        return tokens==null||tokens.isEmpty()?false:tokens.contains(token);
    }

    @Override
    public String getLatestToken()
    {
        if(tokens==null)
        {
            tokens=new ArrayList<String>(0);
            tokens.add(nextToken());            
        }
        else
        {
            tokens.add(nextToken());
        }

        return tokens==null||tokens.isEmpty()?"":tokens.get(tokens.size()-1);
    }

    @Override
    public boolean unsetToken(String token)
    {                
        return !StringUtils.isNotBlank(token)||tokens==null||tokens.isEmpty()?false:tokens.remove(token);
    }

    @Override
    public void unsetAllTokens()
    {
        if(tokens!=null&&!tokens.isEmpty())
        {
            tokens.clear();
        }
    }
}

And interface which it implements,

package token;

import java.io.Serializable;

public interface SessionTokenService extends Serializable
{
    public boolean isTokenValid(String token);
    public String getLatestToken();
    public boolean unsetToken(String token);
    public void unsetAllTokens();
}

And this bean was configured in the application-context.xml file as follows.

<bean id="sessionTokenCleanerService" class="token.SessionToken" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

Now, I'm injecting this service in the following class.

package token;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public final class PreventDuplicateSubmission
{    
    @Autowired
    private final SessionTokenService sessionTokenService=null;

    @Scheduled(fixedDelay=3600000)
    public void clearTokens()
    {
        System.out.println("Scheduled method called.");
        sessionTokenService.unsetAllTokens();            
    }
}

And in the application-context.xml file,

<bean id="preventDuplicateSubmissionService" class="token.PreventDuplicateSubmission"/>

Both of the above beans are annotated with the @Service and they should be part of context:component-scan in the dispatacher-servelt.xml file (or whatever its name).

The method annotated with the @Scheduled annotation in the preceding code snippet is invoked regularly at the given rate but it causes the following obvious exception to be thrown.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.sessionTokenCleanerService': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:343)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:33)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:184)
    at $Proxy79.unsetAllTokens(Unknown Source)
    at token.PreventDuplicateSubmission.clearTokens(PreventDuplicateSubmission.java:102)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:64)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:53)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
    at org.springframework.web.context.request.SessionScope.get(SessionScope.java:90)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:329)
    ... 19 more

To clear data stored in a user's session, the method that does this task should be invoked regularly at a defined time interval from every user's session which is not the case with this attempt. What is the way? The question may simply be : how to trigger a regular time interval from every user's session? Does Spring or Servlet API support something to accomplish this?

like image 425
Tiny Avatar asked Apr 20 '13 20:04

Tiny


2 Answers

Comments

Congrats on using tokens to prevent resubmits ("Introduce Synchronizing Token" refactoring from the book Core J2EE Patterns). :^)

Quartz is valuable for complicated or precise scheduling. But your requirements don't need Quartz. Might be more useful to learn CDI, java.util.Timer, ScheduledExecutor and/or EJB timers.

If using a scheduler, as you mention, it's good to have a singleton scheduler shared by all users, rather than having a scheduler & thread instance per user session.

Be careful if you ever store references to HttpSession or pass it as a parameter. Storing references prevents garbage collection when the session is complete - causing a (big?) memory leak. Attempts to clean up references using HttpSessionListener & other tricks, may not work & are messy. Passing HttpSession to methods as a parameter makes them artificially dependent on the Servlet container and difficult to unit test, because you have to mock complex HttpSession objects. It's cleaner to wrap all session data in a singular object UserSessionState - store refererences to this in your session & object instance variables. This is also known as the Context Object Pattern - i.e. store all your scoped data in one/few POJO context objects, independent from the HTTP protocol classes.

Answer

I suggest two alternative solutions to your problem:

  1. Use a java.util.Timer singleton instance

    You could replace java.util.Timer (introduced in Java SE 1.3) with ScheduledExecutor (introduced in Java SE 5) for a nearly identical solution.

    This is available with the JVM. It requires no jar setup and no configuration. You call timerTask.schedule, passing in a TimerTask instance. When the schedule is due, timerTask.run is called. You remove scheduled tasks via timerTask.cancel and timerTask.purge which releases used memory.

    You code the TimerTask, including it's constructor and you create a new instance and pass it to the schedule method, meaning you can store any data or object references you need within the TimerTask; you can keep a reference to it and call setter methods anytime later.

    I suggest you create two global singletons: a Timer instance and a TimerTask instance. In your custom TimerTask instance, keep a list of all user sessions (in POJO form like UserSessionState or Spring/CDI bean, not in form of HttpSession). Add two methods to this class:addSessionObject and removeSessionObject, with a parameter of UserSessionState or similar. In the TimerTask.run method, iterate through the set of UserSessionState and clear the data.

    Create a custom HttpSessionListener - from sessionCreated, put a new UserSessionState instance into the session and call TimerTask.addUserSession; from sessionDestroyed, call TimerTask.removeUserSession.

    Call the global-scoped singleton timer.schedule to schedule the TimerTask instance to clear the session-scoped reference's contents.

  2. Use Token List with Capped Size (no cleanup)

    Don't cleanup tokens based on time elapsed. Instead restrict the list size (e.g. 25 tokens) and store the most recently generated tokens.

    This is probably the simplest solution. When you add an element to the list, check whether you've exceeded the maximum size, if so wrap-around and insert from the earliest index in the list:

    if (++putIndex > maxSize) {
        putIndex = 0;
    }
    list.put(putIndex, newElement);
    

    Here no scheduler is required, and there's no need to form & maintain a set of all user sessions.

like image 174
Glen Best Avatar answered Sep 28 '22 00:09

Glen Best


I think this should be a lot simpler.

About Synchronizer Token Implementation

  1. The tokens in the synchronizer token pattern are NOT meant to be re-usable. A token is considered valid only for one submission. No more no less.

  2. At any given point of time, you should have to save only one token for a session.

  3. When a form is shown to the user the token should be included in the form as a hidden form element.

  4. On submission, all you have to do is check if the token in the form and the session match. If they do, allow the form submission, and reset the value of the token in the session.

  5. Now if the user re-submits the same form, (with the old token) tokens will not match and you can detect a double submission or a stale submission

  6. On the other hand, if the user reloads the form itself, the updated token will now be present in the hidden form element.

Conclusion - there is no need to save a list of tokens for a user. One token per session is just what is needed. This pattern is widely used as a security measure to prevent CSRF attacks. Where each link on the page can only be invoked once. Even this can be done with just one token per session. For reference you can see how the CSRF Guard V3 works https://www.owasp.org/index.php/Category:OWASP_CSRFGuard_Project#Source_Code

About sessions

  1. A session object has meaning only as long as the thread of execution is somehow tied to an http request / response pair. Or simply put, as long as the user is browsing your site.

  2. Once the user is gone, so does the session (from the JVM). So you cannot use a timer to reset it

  3. Sessions are serialized by servers to make sure they can be sprung to life when a user re-visits your site (jsessionid is used to id which session should be deserialized for which browser session)

  4. There is a timeout associated with a session, if the time out expires, the server starts a new session when the user revisits.

Conclusion - no plausible reason to have to flush users' sessions periodically - And no way to do it.

Let me know if I misunderstood something, and I hope this helps.

like image 21
Akshay Avatar answered Sep 27 '22 23:09

Akshay