25

How can I enable samesite for my web application which runs on wildfly as. Checked standalone.xml however could not find an appropriate tag within

<servlet-container name="default">
    <session-cookie http-only="true" secure="true"/>
    <jsp-config/>
</servlet-container>
Community
  • 1
  • 1
webyildirim
  • 587
  • 3
  • 12
  • 32
  • 1
    Have You considered use Spring Session that supports samesite cookie from version 2.1.0.RELEASE? See https://github.com/spring-projects/spring-session/blob/2.1.0.RELEASE/spring-session-core/src/main/java/org/springframework/session/web/http/DefaultCookieSerializer.java#L85 – snieguu May 17 '19 at 19:58
  • check this one which used GenericFilterBean / temporary redirect request to solve a same kind of issue https://stackoverflow.com/questions/63939078/how-to-set-samesite-and-secure-attribute-to-jsessionid-cookie/63939775#63939775 – ThilankaD Oct 28 '20 at 05:15
  • I had trouble with the accepted solution due to the "Set-Cookie" header not being present for any of the calls. As such I tried another solution from StackOverflow, of course changing the flags as needed: [Adding flags to existing Jsessionid cookie](https://stackoverflow.com/questions/16398327/set-httponly-and-secure-flags-on-session-cookie-in-google-app-engine) – Annanraen Feb 05 '21 at 09:25

6 Answers6

33

Spring Boot 2.6.0

Spring Boot 2.6.0 now supports configuration of SameSite cookie attribute:

Configuration via properties

server.servlet.session.cookie.same-site=strict

Configuration via code

import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {
  @Bean
  public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
  return CookieSameSiteSupplier.ofStrict();
  }
}

Spring Boot 2.5.0 and below

Spring Boot 2.5.0 doesn't support SameSite cookie attribute and there is no setting to enable it.

As for now the Java Servlet 4.0 specification doesn't support the SameSite cookie attribute. You can see available attributes by opening javax.servlet.http.Cookie java class.

However, there are a couple of workarounds. You can override Set-Cookie attribute manually.

Approach #1 (using custom Spring HttpFirewall and wrapper around request):

You need to wrap request and adjust cookies right after session is created. You can achieve it by defining the following classes:

one bean (You can define it inside SecurityConfig if you want to hold everything in one place. I just put @Component annotation on it for brevity)

package hello.approach1;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.stereotype.Component;

@Component
public class CustomHttpFirewall implements HttpFirewall {

    @Override
    public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
        return new RequestWrapper(request);
    }

    @Override
    public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
        return new ResponseWrapper(response);
    }

}

first wrapper class

package hello.approach1;

import java.util.Collection;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.http.HttpHeaders;
import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * Wrapper around HttpServletRequest that overwrites Set-Cookie response header and adds SameSite=None portion.
 */
public class RequestWrapper extends FirewalledRequest {

    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public RequestWrapper(HttpServletRequest request) {
        super(request);
    }

    /**
     * Must be empty by default in Spring Boot. See FirewalledRequest.
     */
    @Override
    public void reset() {
    }

    @Override
    public HttpSession getSession(boolean create) {
        HttpSession session = super.getSession(create);

        if (create) {
            ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (ra != null) {
                overwriteSetCookie(ra.getResponse());
            }
        }

        return session;
    }

    @Override
    public String changeSessionId() {
        String newSessionId = super.changeSessionId();
        ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (ra != null) {
            overwriteSetCookie(ra.getResponse());
        }
        return newSessionId;
    }

    private void overwriteSetCookie(HttpServletResponse response) {
        if (response != null) {
            Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
            boolean firstHeader = true;
            for (String header : headers) { // there can be multiple Set-Cookie attributes
                if (firstHeader) {
                    response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=None")); // set
                    firstHeader = false;
                    continue;
                }
                response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=None")); // add
            }
        }
    }
}

second wrapper class

package hello.approach1;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/**
 * Dummy implementation.
 * To be aligned with RequestWrapper.
 */
public class ResponseWrapper extends HttpServletResponseWrapper {
    /**
     * Constructs a response adaptor wrapping the given response.
     *
     * @param response The response to be wrapped
     * @throws IllegalArgumentException if the response is null
     */
    public ResponseWrapper(HttpServletResponse response) {
        super(response);
    }
}

Approach #2 (using Spring's AuthenticationSuccessHandler):

This approach doesn't work for basic authentication. In case basic authentication, response is flushed/committed right after controller returns response object, before AuthenticationSuccessHandlerImpl#addSameSiteCookieAttribute is called.

package hello.approach2;

import java.io.IOException;
import java.util.Collection;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        addSameSiteCookieAttribute(response);    // add SameSite=strict to Set-Cookie attribute
        response.sendRedirect("/hello"); // redirect to hello.html after success auth
    }

    private void addSameSiteCookieAttribute(HttpServletResponse response) {
        Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
        boolean firstHeader = true;
        for (String header : headers) { // there can be multiple Set-Cookie attributes
            if (firstHeader) {
                response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
                firstHeader = false;
                continue;
            }
            response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
        }
    }
}

Approach #3 (using javax.servlet.Filter):

This approach doesn't work for basic authentication. In case basic authentication, response is flushed/committed right after controller returns response object, before SameSiteFilter#addSameSiteCookieAttribute is called.

package hello.approach3;

import java.io.IOException;
import java.util.Collection;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpHeaders;

public class SameSiteFilter implements javax.servlet.Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
        addSameSiteCookieAttribute((HttpServletResponse) response); // add SameSite=strict cookie attribute
    }

    private void addSameSiteCookieAttribute(HttpServletResponse response) {
        Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
        boolean firstHeader = true;
        for (String header : headers) { // there can be multiple Set-Cookie attributes
            if (firstHeader) {
                response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
                firstHeader = false;
                continue;
            }
            response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
        }
    }

    @Override
    public void destroy() {

    }
}

Approach #4 (if you are using Tomcat 9.0.21 / Tomcat 8.5.42 or above versions)

In your web application, inside the META-INF folder create a context.xml file with the following inside:

<Context>
   <CookieProcessor sameSiteCookies="strict" />
</Context>

Setting the SameSite to none is available starting from Tomcat 9.0.28 / Tomcat 8.5.48)

See this pull request for more details.

Demo project

You can look at this demo project on the GitHub for more details on the configuration for the first 3 approaches.

The SecurityConfig contains all the necessary configuration.

Using addHeader is not guaranteed to work because basically the Servlet container manages the creation of the Session and Cookie. For example, the second and third approaches won't work in case you return JSON in response body because application server will overwrite Set-Cookie header during flushing of response. However, second and third approaches will work in cases, when you redirect a user to another page after successful authentication.

Pay attention that Postman doesn't render/support SameSite cookie attribute under Cookies section (at least at the time of writing). You can look at Set-Cookie response header or use curl to see if SameSite cookie attribute was added.

Eugene Maysyuk
  • 2,977
  • 25
  • 24
  • After 20 hours of debugging, I came across this answer. Issue solved. Thanks a ton, Eugene – Venky Feb 25 '20 at 13:02
  • @Venky I'm glad I could help – Eugene Maysyuk Feb 25 '20 at 19:41
  • 1
    How should this work with doFilter being run before the cookie is modified? I'm trying this and my cookies are not being modified whatever I do – Lightheaded Mar 25 '20 at 12:53
  • @Lightheaded, make sure you call addSameSiteCookieAttribute() after doFilter() method. This ensures that request has been processed by servlet and "Set-Cookie" header is already present in response (and ready for overwriting). – Eugene Maysyuk Mar 25 '20 at 14:27
  • Alright, I guess my issue is elsewhere (e.g Tomcat overwriting the cookie). Thanks! – Lightheaded Mar 25 '20 at 20:03
  • @Lightheaded, could you please describe how it happened in your case, that Tomcat overwritten "Set-Cookie" header, when you have more information about the issue? I might need to update the post with additional info. BTW, do you have some sample app so I could reproduce the issue you faced? – Eugene Maysyuk Mar 26 '20 at 07:20
  • The app I was dealing with was quite a large one and with older versions of spring (but not spring boot), so, unfortunately, I don't have examples. In my case any modifications I did to the cookie programmatically didn't propagate (i.e. `.addHeader()` had no effect at all). In the end I went for moving the backend to the same domain and didn't find a way to resolve the root cause. – Lightheaded Mar 27 '20 at 12:53
  • 1
    @Lightheaded, would it be possible for you to create a sample project that reproduces your problem and upload it to github? I could take a look at this and see if I can help you. – Eugene Maysyuk Mar 27 '20 at 14:46
  • Unfortunately, it's a major undertaking for me to create an example project. I will do so if I have time in the coming weeks, but as I have decided to rewrite the application anyway, it's most likely not a wise time investment for me at the moment. I greatly appreciate your offer to help, though! Thanks for that! – Lightheaded Mar 29 '20 at 18:26
  • Do you know why my variable headers is always empty even if I receive the Set-Cookie header in the inspector ? – WaLinke Jul 16 '20 at 15:11
  • @WaLinke, can I take a look at your code? Do you have sample of code on GitHub? – Eugene Maysyuk Jul 17 '20 at 17:04
  • 1
    Headers are sent first in http request, so if the response has already been (partially) flushed to the client this will not work – cocorossello Aug 31 '20 at 08:57
  • we have only jsp pages without servlet, how fix this in jsp? – Hosein Aqajani Sep 26 '20 at 10:48
  • @EugeneMaysyuk I'm using the first approach and unfortunately "Set-Cookie" header is not present in response yet... I opened a question my classes are there, could you please help me on this? Ty! https://stackoverflow.com/questions/67320068/how-to-set-samesite-none-in-jsessionid-cookie – Andre Apr 29 '21 at 20:54
  • @Andre, I replied under your question. – Eugene Maysyuk Apr 30 '21 at 12:44
  • All these solutions DONT add the SameSiteAttribute to the JSESSIONID-Cookie but instead create a new cookie named "SameSite". I am having the same issue and while testing these suggestions I noticed that. Using SpringSecurity, no SpringBoot. – Vortilion Apr 26 '23 at 08:22
  • @Vortilion they actually add JSESSIONID cookie. take a look at demo code on GitHub. you can run code as Spring Boot app. – Eugene Maysyuk Apr 27 '23 at 15:59
  • For me they dont. – Vortilion Apr 28 '23 at 09:39
  • @Vortilion what version of Spring Boot are you using? – Eugene Maysyuk Apr 28 '23 at 11:19
  • @Vortilion Can you provide a sample project on the GitHub so I could take a look at what is not working in your case? – Eugene Maysyuk Apr 28 '23 at 11:20
9

One workaround is to hack the SameSite setting into the cookie by using another attribute (e.g. comment):

<servlet-container name="default">
    <jsp-config/>
    <session-cookie comment="; SameSite=None"/>
    <websockets/>
</servlet-container>

But because Undertow quotes the comment (and other) values when using version 0 or version 1 cookies, JBoss/WildFly needs to be running with the io.undertow.cookie.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION system property set to true:

 ./bin/standalone.sh -Dio.undertow.cookie.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION=true

This will give you the desired result: cookies

This approach is obviously hacky, and relies entirely on Undertow implementation details, so I'd recommend configuring on the web server or load balancer level instead.

Robby Cornelissen
  • 91,784
  • 22
  • 134
  • 156
  • 1
    Alternatively you can set the attribute rfc6265-cookie-validation=true in the subsystem-> server->default-server->http-listener tag – Rahil Husain Aug 27 '20 at 10:17
8

For Spring Boot with the currently latest release:

If you do not have the latest spring-boot-starter-tomcat check the SameSiteCookies enum for value UNSET, if the value is missing you need a newer release because it will skip the value SameSite=None.

@Component
public class SameSiteTomcatCookieProcessorCustomizationBean implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>
{
    @Override
    public void customize(TomcatServletWebServerFactory server) {

        server.getTomcatContextCustomizers().add(new TomcatContextCustomizer()
        {
            @Override
            public void customize(Context context)
            {
                Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
                cookieProcessor.setSameSiteCookies("None");
                context.setCookieProcessor(cookieProcessor);
            }
        });
    }
}
Taazar
  • 1,545
  • 18
  • 27
Gábor Ibolya
  • 81
  • 1
  • 1
2

My workaround, which works in JBoss EAP 7.2, is a custom handler. I use it as a global handler. But you can also use it in the jboss-web.xml. You need to play with the cookie implementation because undertow only allows Strict or Lax for samesite (it throws '"UT000162: Same-site attribute None is invalid. It must be Strict or Lax"' if you use cookie.setSameSiteMode("None"))

import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.Cookie;
import java.lang.reflect.Proxy;
import java.util.Map;

public class CookieSameSiteHandler implements HttpHandler
{
   private  HttpHandler next;

   public CookieSameSiteHandler(HttpHandler next){
      this.next = next;
   }

   @Override
   public void handleRequest(final HttpServerExchange exchange)
      throws Exception
   {
      exchange.addResponseCommitListener(serverExchange -> {
         for (Map.Entry<String, Cookie> responcecookie : serverExchange.getResponseCookies().entrySet()){
            serverExchange.getResponseCookies().replace(responcecookie.getKey(), proxyCookie(responcecookie.getValue()));
         }
      });
      next.handleRequest(exchange);
   }

   private Cookie proxyCookie(Cookie cookie)
   {
      return (Cookie)Proxy.newProxyInstance(
         cookie.getClass().getClassLoader(),
         cookie.getClass().getInterfaces(),
         (proxy, method, args) -> {
            if ("isSameSite".equals(method.getName())){
               return true;
            }
            if ("getSameSiteMode".equals(method.getName()) && cookie.getSameSiteMode() == null){
               return "None";
            }
            if ("isSecure".equals(method.getName()) && cookie.getSameSiteMode() == null){
               return true;
            }
            return method.invoke(cookie, args);
         });
   }
}

handler configuration:

<subsystem xmlns="urn:jboss:domain:undertow:7.0" default-virtual-host="default-host">
    <buffer-cache name="default"/>
    <server name="default-server" default-host="default-host">
        ...
        <host name="default-host" alias="localhost,example.com">
            ...
            <filter-ref name="cookiehandler"/>
            ...
        </host>
    </server>
    ...
    <filters>
        <filter class-name="nl.myownstuff.handler.CookieSameSiteHandler" module="nl.myownstuff.undertow" name="cookiehandler"/>
    </filters>
</subsystem>
Ton Bosma
  • 171
  • 1
  • 6
  • With WildFly 19.1 and above you no longer need to write your own class. This can be achieved with an `expression-filter` configured via `standalone.conf` as described in [this answer](https://stackoverflow.com/a/72458931). – Martin Höller Jun 07 '22 at 07:19
2

Solution for Wildfly 19.1.0 and later:

$ cat src/main/webapp/WEB-INF/undertow-handlers.conf
samesite-cookie(mode=Lax)

Resource: https://www.wildfly.org/news/2020/05/04/WildFly-1910-Released/

  • 1
    This can (since WildFly 19.1) also be configured via `standalone.conf` resp. via CLI. See [my answer](https://stackoverflow.com/a/72458931) to a similar question. – Martin Höller Jun 01 '22 at 08:55
2

If you are using WildFly 19 or newer, the recommended approach is to define the SameSite Policy in the undertow-handlers.conf. This is quite flexible as you can define the Web context, under which it will be used the SameSite Policy, and a regular expression pattern for the cookies. Example:

path(/app2)->samesite-cookie(mode=Lax, cookie-pattern=abc*)

On the other hand, for Tomcat applications, you can add a META-INF/context.xml file with the sameSiteCookies attribute, as in this example:

<Context>
   <CookieProcessor sameSiteCookies="strict" />
</Context>

Some References: https://github.com/apache/tomcat/pull/162

How to set SameSite attribute in Web applications

Francesco Marchioni
  • 4,091
  • 1
  • 25
  • 40