Search code examples
javaspringspring-sessiongemfirespring-data-gemfire

Container's session object and not GemFire's session object is invoked inside Servlet Filter


From a custom Servlet Filter when trying to access a GemFire session object, it is taking the Container's session object instead. The session object is of the type:

org.apache.catalina.session.StandardSessionFacade@517957e2

But from the Controller, it is working fine. The session object is of the type: org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper$HttpSessionWrapper@5afe18ce

On to how we configured GemFire:

We have a legacy retail application. On top, we have used the 2.0.5 version of GemFire. On the webappintializer startup,

AnnotationConfigWebApplicationContext rootContext = 
    new AnnotationConfigWebApplicationContext();

rootContext.register(GemfireConfig.class,RootConfig.class, SecurityConfig.class);

Since the springSessionRepositoryFilter bean was not added to the filter chain, we had to explicitly register the filter with DelegatingFilterProxy using the below:

FilterRegistration.Dynamic springSessionRepositoryFilter = 
    container.addFilter("springSessionRepositoryFilter", DelegatingFilterProxy.class);

springSessionRepositoryFilter.addMappingForUrlPatterns(
    EnumSet.allOf(DispatcherType.class), false, "/*");

On the data handling side in order to get the session object, we have a getSession method which returns a session object:

ServletRequestAttributes attr = 
    (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();

HttpSession session = attr.getRequest().getSession();

When we call the getSession() method from Controller it is working absolutely fine as designed. But calling the same from a Servlet Filter eventually gets the Container created session object.

Any help is much appreciated.

Reworked as per comment by @John Blum but still facing the same issue.


Solution

  • In a nutshell, in order for Spring Session, and specifically, Spring Session for Pivotal GemFire (SSDG), to do its job, the SessionRepositoryFilter (Javadoc, Source) must be the first Servlet Filter in the filter chain when registered with your (Web) Application Container (e.g. Apache Tomcat, Eclipse Jetty, etc).

    Otherwise, if Spring Session's SessionRepositoryFilter is not the first Servlet Filter in the filter chain, then Spring Session will not have intercepted the HTTP request (yet) and won't be able to exercise its opportunity to replace the Container session (by wrapping the HttpServletRequest with SessionRepositoryFilter.SessionRepositoryRequestWrapper, see here) with a Session provided by Spring Session using the appropriate provider (e.g. like GemFire with SSDG), as determined by the SessionRepository implementation, which is set on the SessionRepositoryFilter (here).

    The reason why your Spring Web MVC application Controller works is, the Java EE Servlet Spec/Container guarantees that all Servlet Filters are invoked before any Servlets are called with the HTTP request (i.e. HttpServletRequest). And, since the Spring Web MVC DispatcherServlet is a proper HttpServlet and is responsible for invoking your application-defined Spring Web MVC Controllers, then the application Controllers are guaranteed to see the "replaced" HTTP request (and by extension, HTTP session object).

    So, a few housekeeping items... I am not (exactly) certain what you mean by:

    1. 2.0.5 version of GemFire. 2.0.5 refers to the latest/current version of Spring Session for Pivotal GemFire.

    2. And webappinitializer?

    For #2, did you mean that you specifically created a Spring WebApplicationInitializer to explicitly register the SessionRepositoryFilter, manually (as shown above in your code snippet)?

    Did you know that Spring Session already provides such a class... o.s.session.web.context.AbstractHttpServletApplicationInitializer.

    This class is responsible for registering the SessionRepositoryFilter using Spring's DelegatingFilterProxy class (here, then here, and here (notice the insertBeforeOtherFilters instance variable, which defaults to true), and in the right order, here, well, specifically, here).

    Interestingly enough, it seems like you are doing the same, or similar, thing in your snippet of Filter registration code above.

    NOTE: this (programmatical configuration of the Servlet container) only works in Servlet 3.0 containers and later.

    You can see how Spring Session's AbstractHttpServletApplicationInitializer gets used in the samples, for instance, here. More details on the Initializer are in the corresponding guide docs for the sample.

    One thing different about your Spring DelegatingFilterProxy class registration (named after the SessionRepositoryFilter bean, named "springSessionRepositoryFilter") that I noticed was, you are passing in the DelegatingFilterProxy.class as the second argument to servletContext.addFilter("filterName", <FilterType>);, as shown here...

    FilterRegistration.Dynamic springSessionRepositoryFilter = 
        container.addFilter("springSessionRepositoryFilter", DelegatingFilterProxy.class);
    

    However, Spring Session (core) itself actually constructs and initializes (with the "springSessionRepositoryFilter" bean name) an instance of the Spring DelegatingFilterProxy class and passes that "instance" to the ServletContext.addFilter(..) method (2nd argument) on registration.

    I suspect the ServletContext.addFilter(..) API itself just uses the default constructor of the Spring DelegatingProxyFilter class when constructing/initializing an instance, which is probably the root of your problem, especially when registering the Servlet Filter programmatically.

    Food for thought.

    Hope this helps!