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>
Enable the new SameSite behavior If you are running Chrome 91 or newer, you can skip to step 3.) Go to chrome://flags and enable (or set to "Default") both #same-site-by-default-cookies and #cookies-without-same-site-must-be-secure. Restart Chrome for the changes to take effect, if you made any changes.
Fixing common warnings The warning appears because any cookie that requests SameSite=None but is not marked Secure will be rejected. To fix this, you will have to add the Secure attribute to your SameSite=None cookies. A Secure cookie is only sent to the server with an encrypted request over the HTTPS protocol.
From spring boot version 2.6. + you may specify your samesite cookie either programatically or via configuration file. This should be the answer for 2022. Upper will cause Spring to bind the attribute into org.
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.
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With