Do you know any Java cookie implementation which allows to set a custom flag for cookie, like SameSite=strict
? It seems that javax.servlet.http.Cookie has a str
Jetty server version 9.4.26.v20200117
allows for setting the SameSite
attribute on the cookie. I had to do some digging around but this works.
import static org.eclipse.jetty.http.HttpCookie.SAME_SITE_STRICT_COMMENT;
...
Cookie cookie = new Cookie("my-cookie", "some-value");
cookie.setMaxAge(120); // age in seconds
cookie.setSecure(true);
cookie.setHttpOnly(true);
cookie.setComment(SAME_SITE_STRICT_COMMENT);
response.addCookie(cookie);
The addCookie()
method on jetty servers's Response
object does a check on the comment to add the SameSite
attribute.
As of today (24.01.20) servlet-api
does not let to set sameSite
attribute to the cookie. BTW there is an ongoing ticket (LINK) which will release a new (5.0 or 5.1 servlet-api
).
Option 1: You are not in a hurry and can wait for servlet-api
version, where Cookie
class and SessionCookieConfig
class have dedicated methods to set sameSite
attribute.
Option 2: You are using an old version of servlet-api
(e.g. 3.1), consequently old version of Tomcat (e.g. I am having current situation now). It means even when community releases servlet-api
with sameSite
support, you can not immediately update you version, because it can be too risky to update couple of major versions.
In this case we have found a solution.
There is a Cookie Processor Component
LINK in Tomcat, which
The CookieProcessor element represents the component that parses received cookie headers into javax.servlet.http.Cookie objects accessible through HttpServletRequest.getCookies() and converts javax.servlet.http.Cookie objects added to the response through HttpServletResponse.addCookie() to the HTTP headers returned to the client.
The usage of this processor is quite straight forward. Inside of context.xml:
<Context>
...
<CookieProcessor sameSiteCookies="none"/>
</Context>
In this case default implementation of processor is used (org.apache.tomcat.util.http.Rfc6265CookieProcessor
), but you can specify any other within an CookieProcessor
attribute className
.
If using spring boot with Tom cat then this has been answered in another question. In summary, set the attribute on the tom cat config. This is global, all cookies will then have same site enabled. (from the other question https://stackoverflow.com/a/60860531/400048)
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
@Bean
public TomcatContextCustomizer sameSiteCookiesConfig() {
return context -> {
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
cookieProcessor.setSameSiteCookies(SameSiteCookies.NONE.getValue());
context.setCookieProcessor(cookieProcessor);
};
}
If you don't wanna update all your code, you can also achieve same by one line config using Apache or Nginx configuration(or any other HTTP server/proxy that you are using)
You can add the following line to your Apache configuration
Header always edit Set-Cookie (.*) "$1; SameSite=Lax"
and this will update all your cookies with SameSite=Lax
flag
See more here: https://blog.giantgeek.com/?p=1872
location / {
# your usual config ...
# hack, set all cookies to secure, httponly and samesite (strict or lax)
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
}
Same here, this also will update all your cookies with SameSite=Lax
flag
See more here: https://serverfault.com/questions/849888/add-samesite-to-cookies-using-nginx-as-reverse-proxy
I tried the listed solutions for using javax.servlet.http.Cookie to set the SameSite=strict
attribute, but none of them worked.
However, this way worked for me, using javax.servlet.http.Cookie (JRE 1.8 + JBOSS 7.X) :
Cookie cookie = new Cookie(name, value);
path = path.concat("SameSite=Strict;");
cookie.setPath(path);
That's it. tested on
If you have existing code, no doubt you've used the java servlet Cookie object. We certainly have, so we wanted the least disruptive option. @kriegaex's answer is clean and concise, but is basically hard coding the cookie and doesn't reuse the cookie object. To expand on his answer, we wrote this function to handle the same site functionality, while at the same time, maintaining the existing Cookie object functionality. This answer is intended to be used in cases where you need to add multiple cookies on your response object, without making changes to existing cookies that may already be on the headers. The other option of course is to write a new cookie class and extend the functionality, but that requires even more changes to existing code than what we've come up with here.
Note that with this solution, only one line of existing code (per cookie) changes in order to add the same site functionality.
Sample usage:
// Existing code that doesn't change:
Cookie cookie1=new Cookie("cookie1",Util.encodeURL(id));
cookie1.setHttpOnly(false);
cookie1.setPath("/");
Cookie cookie2=new Cookie("cookie2",Util.encodeURL(id));
cookie2.setHttpOnly(false);
cookie2.setPath("/");
// Old Code that is replaced by new code
// httpResponse.addCookie(cookie1);
// httpResponse.addCookie(cookie2);
// New Code - see static helper class below
HttpService.addCookie(httpResponse, cookie1, "none");
HttpService.addCookie(httpResponse, cookie2, "Strict");
Example response headers when using cURL:
< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow/1
< Set-Cookie: cookie1=f871c026e8eb418c9c612f0c7fe05b08; path=/; SameSite=none; secure
< Set-Cookie: cookie2=51b405b9487f4487b50c80b32eabcc24; path=/; SameSite=Strict; secure
< Server: WildFly/9
< Transfer-Encoding: chunked
< Content-Type: image/png
< Date: Tue, 10 Mar 2020 01:55:37 GMT
And finally, the static helper class:
public class HttpService {
private static final FastDateFormat expiresDateFormat= FastDateFormat.getInstance("EEE, dd MMM yyyy HH:mm:ss zzz", TimeZone.getTimeZone("GMT"));
public static void addCookie(HttpServletResponse response, Cookie cookie, String sameSite) {
StringBuilder c = new StringBuilder(64+cookie.getValue().length());
c.append(cookie.getName());
c.append('=');
c.append(cookie.getValue());
append2cookie(c,"domain", cookie.getDomain());
append2cookie(c,"path", cookie.getPath());
append2cookie(c,"SameSite", sameSite);
if (cookie.getSecure()) {
c.append("; secure");
}
if (cookie.isHttpOnly()) {
c.append("; HttpOnly");
}
if (cookie.getMaxAge()>=0) {
append2cookie(c,"Expires", getExpires(cookie.getMaxAge()));
}
response.addHeader("Set-Cookie", c.toString());
}
private static String getExpires(int maxAge) {
if (maxAge<0) {
return "";
}
Calendar expireDate = Calendar.getInstance();
expireDate.setTime(new Date());
expireDate.add(Calendar.SECOND,maxAge);
return expiresDateFormat.format(expireDate);
}
private static void append2cookie(StringBuilder cookie, String key, String value) {
if (key==null ||
value==null ||
key.trim().equals("")
|| value.trim().equals("")) {
return;
}
cookie.append("; ");
cookie.append(key);
cookie.append('=');
cookie.append(value);
}
}