7

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?

Community
  • 1
  • 1
Tiny
  • 27,221
  • 105
  • 339
  • 599
  • Tried to define the `PreventDuplicateSubmission` bean as a session scoped bean with `` but it didn't even invoke the method annotated with `@Scheduler` in the same bean. – Tiny Apr 23 '13 at 01:43
  • How do you load your application context. Try to load it with ContextLoaderListener -> http://www.mkyong.com/spring3/spring-3-mvc-hello-world-example/ (5. Integrate Web application with Spring) – Evgeni Dimitrov Apr 23 '13 at 06:58
  • That's looks similar. My `web.xml` file is available [here](http://pastebin.ca/2365347). – Tiny Apr 24 '13 at 06:29
  • could you solve the problem? – Francisco Spaeth Apr 25 '13 at 20:20

7 Answers7

9

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.

Glen Best
  • 22,769
  • 3
  • 58
  • 74
3

My Idea would be something like:

Short version:

  1. change task interval to 1 minute;
  2. add unsetAllTokensIfNeeded method to SessionTokenService that will be invoked every minute but will just clean the tokens list if it is really needed (in this case the decision is done based on the time).

Detailed Version:

Your scheduled task will run every minute invoking the unsetAllTokensIfNeeded method implemented by SessionToken. The method implementation will check the last time token list was clean and invoke unsetAllTokens if it was an hour ago.

But in order to invoke it in every single session scoped SessionTokenService you will need the list of existing SessionTokenServices, that could be achieved by registering it at creation time to the service that will clean it (here you shall use a WeakHashMap to avoid hard references, this would avoid the objects to be collected by garbage collector).

The implementation would be something like this:

Session Tokens:

Interface:

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

Implementation:

@Service
@Scope("session")
public final class SessionToken implements SessionTokenService, DisposableBean, InitializingBean {

    private List<String> tokens;
    private Date lastCleanup = new Date();

// EDIT {{{

    @Autowired
    private SessionTokenFlusherService flusherService;

    public void afterPropertiesSet() {
        flusherService.register(this);
    }

    public void destroy() {
        flusherService.unregister(this);
    }

// }}}

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

        return 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();
            lastCleanup = new Date();
        }
    }

    @Override
    public void unsetAllTokensIfNeeded() {
        if (lastCleanup.getTime() < new Date().getTime() - 3600000) {
           unsetAllTokens();
        }
    }

}

Session Token Flusher:

Interface:

public interface SessionTokenFlusherService {
    public void register(SessionToken sessionToken);
    public void unregister(SessionToken sessionToken);
}

Implementation:

@Service
public class DefaultSessionTokenFlusherService implements SessionTokenFlusherService {

    private Map<SessionToken,Object> sessionTokens = new WeakHashMap<SessionToken,Object>();

    public void register(SessionToken sessionToken) {
        sessionToken.put(sessionToken, new Object());
    }

    public void unregister(SessionToken sessionToken) {
        sessionToken.remove(sessionToken);
    }

    @Scheduled(fixedDelay=60000) // each minute
    public void execute() {
        for (Entry<SessionToken, Object> e : sessionToken.entrySet()) {
            e.getKey().unsetAllTokensIfNeeded();
        }
    }

}

In this case you shall use the annotation driven task feature from Spring:

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

From my point of view this would be a simple and good solution.

EDIT

With this approach you are more or less registering the SessionToken to the Flusher when spring ends the bean configuration and removing it when spring destroys the bean, this is done when the session is terminated.

Francisco Spaeth
  • 23,493
  • 7
  • 67
  • 106
  • Hi, this works as it is but objects contained by `WeakHashMap` remain after a user logs out. It could be a long list as time goes by. Is it appropriate to leave them to garbage collector to be reclaimed? Also, one small thing that I don't know, at this line - `flusherService.register(this);` inside the constructor of the `SessionToken` class, a warning appears - `Leaking this in constructor`. Can this be suppressed somehow? – Tiny Apr 24 '13 at 06:37
  • Yes, therefore I changed a bit the sample, let `SessionToken` implement `DisposableBean` and `InitializingBean`, this will register the bean just after spring finish bean configuration (avoid the leaking this in constructor message) and unregistering it when session is terminated, I hope it helps. – Francisco Spaeth Apr 24 '13 at 07:53
  • Hello sir, the concrete problem was solved (with reference to the comment below the question). I was however still looking for another condition inside the `unsetAllTokensIfNeeded()` method. What happens is that this condition `if (lastCleanup.getTime() > new Date().getTime() - 3600000)` is evaluated to true forever, once it is evaluated to true, not a major issue though. Moreover, I was getting an exception indicating `WeakHashMap` is not serializable. This answer is quite useful in the future too, please don't remove it. – Tiny Apr 26 '13 at 08:53
  • I just edit it, I asked because I'm curious if this was what you needed, if it covers your requirements... – Francisco Spaeth Apr 26 '13 at 11:13
  • @JavaMentor : Session scoped beans require implementing the `java.io.Serializalbe` interface. Don't they? Regarding the OP's [comment](http://stackoverflow.com/questions/16124813/how-to-delete-session-data-at-a-regular-time-interval-every-hour#comment23219298_16166208) - "*I was getting an exception indicating `WeakHashMap` is not serializable*". – Lion May 03 '13 at 21:39
  • @Lion the flusher service isn't session scoped (it is a kind of scheduled service that runs every minute and just clear the SessionTokens that are stored within a `SessionTokenService` and this one is session scoped). – Francisco Spaeth May 04 '13 at 07:31
3

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.

Akshay
  • 3,158
  • 24
  • 28
  • if the user does have more than one tab, and they submit both forms, that should be treated like a double submit! – Akshay Apr 24 '13 at 06:54
  • If you do want to maintain more than one token per session, maintain a fini size list like 10 tokens. When there is a need for the 11th token, remove the first one. In any case you cannot and should not think about flushing sessions periodically through code. My answer still holds.you will not be able to run a job to delete sessions. – Akshay Apr 27 '13 at 20:07
  • @AkshaySinghal : The OP is asking about removing just a session attribute periodically and not flushing out the entire user session as you might seem to have assumed he meant. – Lion Apr 29 '13 at 15:44
  • I did not assuming flushing out the entire session, I guess my comment might have given that impression. Thanks for bringing it out, I appreciate you trying to make sure I understood the question properly. Regardless, trying to update a browser session after the user is long gone is not possible. So my response to the question still remains the same. – Akshay Apr 29 '13 at 15:55
2

I don't know much about quartz, but that's what I would do in your situation: Spring 3.2, right? In your application context:

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

You will need task namespace...

In some class(SessionCleaner.java for example):

    @Scheduled(fixedRate=3600000)
    public void clearSession(){
         //clear the session    
    }

What I would do is to use the session data as session scoped bean managed by Spring:

<bean id="mySessionData" class="MySessionDataBean" scope="session">
</bean>

and then inject it where I need it. Then the clan will look like that.

class SessionCleaner{
        @Autowired
        private MySessionDataBean sessionData;

        @Scheduled(fixedRate=3600000)
        public void clearSession(){
            sessionData.getList().clear();//something like that    
        }
}
Evgeni Dimitrov
  • 21,976
  • 33
  • 120
  • 145
1

I do not know much about Quartz .But ,seeing yours exception what i can guess is scheduling is a part of asynchronous communication. So Multithreading and Event driven model are used to achieve it. So,in your case you are trying to crate a bean preventDuplicateSubmissionService .The container creates bean as a part of object.But when you are accessing it, the thread created by Quartz as a part of asynchronous scheduling is trying to get a service.But,for that thread the bean is not instantiated.May be you are using scope="request"> .So when scope is request for every http request its object is created.In your case when you try to access bean then the bean with request scope is not created because the accessing of bean is made in non http mode,so when thread try to aceess a service the exception is thrown.I also had passed from same problem when i was trying to access a bean using multithreading in serviceImpl.in my case the bean for request scope was not created because it is created for every http request mode. I implemented solutin given here and it works for me. Accessing scoped proxy beans within Threads of

Community
  • 1
  • 1
abishkar bhattarai
  • 7,371
  • 8
  • 49
  • 66
1

It seems the token remover periodic job you setup did not run in the context of a particular user session -- nor all user sessions.

I'm not sure if there's any mechanism for you to scan through all active sessions and modify it, however the alternative I'm proposing is:

Store a pair of unique token with a timestamp on the server side. Only send the token to the user (never the timestamp) when you present the form. When the form is submitted, lookup when that token is generated -- if it's past the timeout value, reject it.

This way you don't even need a timer to delete all the token. Using a timer would also delete newly created token

gerrytan
  • 40,313
  • 9
  • 84
  • 99
1

In THe original Question method (which was running correctly as per shcedule) use below code...

package quartz;

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

public final class RemoveTokens extends QuartzJobBean implements javax.sevlet.http.HttpSessionListener {

    static java.util.Map<String, HttpSession> httpSessionMap = new HashMap<String, HttpSession>();

    void sessionCreated(HttpSessionEvent se) {
        HttpSession ss = se.getSession();
        httpSessionMap.put(ss.getId(), ss);
    }

    void sessionDestroyed(HttpSessionEvent se) {
        HttpSession ss = se.getSession();
        httpSessionMap.remove(ss.getId());
    }

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        Set<String> keys = httpSessionMap.keySet();

        for (String key : keys) {
            HttpSession ss = httpSessionMap.get(key);
            Long date = (Long) ss.getAttribute("time");

            if (date == null) {
                date = ss.getCreationTime();
            }

            long curenttime = System.currentTimeMillis();
            long difference = curenttime - date;

            if (difference > (60 * 60 * 1000)) {
                /*Greater than 1 hour*/
                List l = ss.getAttribute("YourList");
                l.removeAll(l);
                ss.setAttribute("time", curenttime);
            }
        }
    }
}
Lion
  • 18,729
  • 22
  • 80
  • 110
rahul maindargi
  • 5,359
  • 2
  • 16
  • 23