Search code examples
springspring-boottomcatspring-securitytomcat8

How to disable HttpOnly flag on Set-Cookie header on login in Spring Boot 2.1.0


I am having issues disabling the httpOnly flag on the set-cookie header. This is mainly an issue on login when the JSESSIONID is being sent back in the response. Note that this is on a tomcat server deployed on AWS EBS.

Any of the configs below work fine locally but no on deployment.

I have tried the following solutions, none seem to work

application.yml config

server:  
  servlet:
    session:
      cookie:
        http-only: false

Servlet Context Initializer

@Bean
open fun servletContextInitializer(): ServletContextInitializer {
    return ServletContextInitializer { servletContext ->
        servletContext.setSessionTrackingModes(setOf(SessionTrackingMode.COOKIE))
        val sessionCookieConfig = servletContext.sessionCookieConfig
        sessionCookieConfig.isHttpOnly = false
    }

WebServerFactoryCustomizer

@Bean
open fun tomcatCustomizer(): WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
    return WebServerFactoryCustomizer { tomcat ->
        tomcat
            .addContextCustomizers(TomcatContextCustomizer { context -> context.useHttpOnly = false })
    }

web.xml

    <session-config>
      <cookie-config>
        <http-only>false</http-only>
      </cookie-config>
    </session-config>

Sample Request Header

Host: 
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: 
Authorization: Bearer null
Content-Type: application/json
Content-Length: 58
Origin: 
Connection: keep-alive
TE: Trailers

Sample Response Header

HTTP/2.0 200 OK
date: Sat, 16 Mar 2019 14:11:58 GMT
set-cookie: AWSALB=qBpX9uFjtkP4H7gyJ3EXL8na0a7aARiEN/twi0cc2sPywvbysKXXaNfQbe8HaS5hcC6VRnkp09VYj0pGcXiHbWRod9OithDlQ0ZIvHSbY7B5xiJT1r8N+lcRdCcp; Expires=Sat, 23 Mar 2019 14:11:57 GMT; Path=/
server: Apache/2.4.37 (Amazon) OpenSSL/1.0.2k-fips
vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers
access-control-allow-origin: 
access-control-allow-credentials: true
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: 0
strict-transport-security: max-age=31536000 ; includeSubDomains
x-frame-options: DENY
set-cookie: JSESSIONID=70F12355ABFDD0F42292D9F6CEAA22BF; Path=/; Secure; HttpOnly
X-Firefox-Spdy: h2

Solution

  • I was finally able to resolve it by creating a Filter that runs as part of Spring Security. The filter executes before the SecurityContextPersistenceFilter, thus waiting until the set-cookie header is added then updates the headers (before in the chain, gets last call after doFilter() executes).

    Filter Implementation

    package com.zambezii.app.security.filter
    
    import org.springframework.web.filter.GenericFilterBean
    import java.io.IOException
    import javax.servlet.FilterChain
    import javax.servlet.ServletException
    import javax.servlet.ServletRequest
    import javax.servlet.ServletResponse
    import javax.servlet.http.HttpServletRequest
    import javax.servlet.http.HttpServletResponse
    
    class SessionFilter : GenericFilterBean() {
    
        @Throws(IOException::class, ServletException::class)
        override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
            val req = request as HttpServletRequest
            val res = response as HttpServletResponse
            chain.doFilter(req, res)
    
            removeHttpOnlyFlag(res)
        }
    
        private fun removeHttpOnlyFlag(res: HttpServletResponse) {
            val setCookieHeaderName = "set-cookie"
            var setCookieHeader = res.getHeader(setCookieHeaderName)
    
            if (setCookieHeader != null) {
                setCookieHeader = setCookieHeader.replace("; HttpOnly", "")
                res.setHeader(setCookieHeaderName, setCookieHeader)
            }
        }
    }
    

    Security Config

    open class WebSecurityConfig() : WebSecurityConfigurerAdapter() {
    
        override fun configure(http: HttpSecurity) {
                ...
                .authenticated()
                .and()
                .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter::class.java)
                .addFilterBefore(SessionFilter(), SecurityContextPersistenceFilter::class.java)