Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enable samesite for jsessionid cookie

Tags:

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> 
like image 609
webyildirim Avatar asked Apr 06 '18 16:04

webyildirim


People also ask

How do I turn on SameSite cookies?

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.

How do I fix the SameSite cookie problem?

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.

How do you set the same site cookie flag in spring boot?

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.


2 Answers

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.

like image 178
Eugene Maysyuk Avatar answered Sep 17 '22 06:09

Eugene Maysyuk


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.

like image 36
Robby Cornelissen Avatar answered Sep 18 '22 06:09

Robby Cornelissen