Search code examples
springtomcatspring-boottomcat8spring-session

Spring Session not working on Tomcat 8 when using Tiles - SESSION Cookie is not set as response is already included


I am using Spring Session 1.2.0.RELEASE on a Spring Boot Project. This is packaged as a war and deployed on Tomcat 8.

I have followed Spring Session documentation and configured it properly. The problem is that the entry point to the application is a controller that sets some value on session but the SESSION cookie is not sent to the browser.

Debugging I see that:

  1. org.springframework.session.web.http.CookieHttpSessionStrategy.onNewSession() tries to write the cookie:

    this.cookieSerializer .writeCookieValue(new CookieValue(request, response, cookieValue));

  2. org.springframework.session.web.http.DefaultCookieSerializer.writeCookieValue() sets the cookie in the response:

    response.addCookie(sessionCookie);

  3. The cookie isn't actually written. The underlying response object is org.apache.catalina.core.ApplicationHttpResponse. Its addCookie() method is:

    /**
      *  Disallow <code>addCookie()</code> calls on an included response.         
      *  @param cookie The new cookie
      */
    @Override
    public void addCookie(Cookie cookie) {
    
        if (!included)
            ((HttpServletResponse) getResponse()).addCookie(cookie);
    
    }
    

The problem is that included attribute, which at some point is set true, preventing the cookie from being added.

This happens when the jsp (using tiles) is being serviced:

enter image description here

UPDATE:

This is the moment when the response is being marked as included (when standard.jsp tiles layout is inserting an attribute:

<tiles:insertAttribute name="header" ignore="false"/>

enter image description here


Solution

  • To work around this problem I ended up creating a filter to enforce the creation of the session.

    As seen, the first call to the controller didn't add the cookie because during the Tiles-JSP rendering the response was already marked as included. What I do is forcing the creation of the session in the filter and redirecting asking the very same requestURI. This way, since the call doesn't involve a tiles rendering the cookie is created and can be used right away in the next calls.

    @Bean
    @ConditionalOnExpression("${sessionEnforcerFilter.enabled:true}")
    public FilterRegistrationBean sessionEnforcerFilter(){
        logger.info("Registering sessionEnforcerFilter");
        FilterRegistrationBean frb = new FilterRegistrationBean();
        frb.setName("sessionEnforcerFilter");
        frb.setFilter(new SessionEnforcerFilter());
        frb.setUrlPatterns(Arrays.asList(new String[]{"/*"}));
        return frb;
    }
    
    public class SessionEnforcerFilter implements Filter{
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {}
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {
            HttpServletRequest httpServletRequest = (HttpServletRequest)request;
            HttpServletResponse httpServletResponse = (HttpServletResponse)response;
            if(httpServletRequest.getSession(false)==null){
                logger.debug("sessionEnforcerFilter.doFilter () - Session is null - forcing its creation");
                httpServletRequest.getSession();
                String requestURI = httpServletRequest.getRequestURI();
                logger.debug("sessionEnforcerFilter.doFilter () - Repeating request [{}]", requestURI);
                httpServletResponse.sendRedirect(requestURI);
            }else{
                chain.doFilter(httpServletRequest, response);
            }
        }
    
        @Override
        public void destroy() {}
    
    }