SpringBoot+SpringSessionでSamesite Cookieを対応したのでメモ。
前提条件
- chromeがSameSiteのデフォルトをLaxに変えたので、これまでSameSiteを付けていない場合はSameSite=Noneを付ける必要がある
- SameSite=Noneの場合に不具合が起こるブラウザがある(chrome51~66やSafariの一部) ※ 以降非互換ブラウザと呼ぶ
- つまUserAgentを見て、非互換ブラウザの場合はSameSiteを付けないようにする必要がある
実装検討過程
- SpringSessionを使っていたのでCookieの設定はDefaultCookieSerializerで行う
- Cookieをwriteしているところで、DefaultCookieSerializerをcloneしてSameSiteだけ書き換えようかと思ったが、DefaultCookieSerializerはgetterを持っていないのでcloneが面倒
- DefaultCookieSerializerをラップして、SameSiteがNone指定の場合にSameSiteを設定しないDefaultCookieSerializerを別に用意しておいて、非互換ブラウザの場合はcookie書き込みの際にそれを使うようにする
実装
import org.springframework.session.web.http.DefaultCookieSerializer;
/**
* SameSiteがNoneの場合に不具合を起こすクライアントに対してSameSite属性を付与しないDefaultCookieSerializer
*/
public class SameSiteIncompatibleSupportCookieSerialzer extends DefaultCookieSerializer {
/**
* SameSite属性非互換ブラウザ向けのDefaultCookieSerializer
*/
private DefaultCookieSerializer sameSiteIncompatibleSupportDefaultCookieSerializer = new DefaultCookieSerializer();
@Override
public void writeCookieValue(CookieValue cookieValue) {
if (非互換ブラウザの場合) {
sameSiteIncompatibleSupportDefaultCookieSerializer.writeCookieValue(cookieValue);
} else {
super.writeCookieValue(cookieValue);
}
}
@Override
public void setUseSecureCookie(boolean useSecureCookie) {
super.setUseSecureCookie(useSecureCookie);
sameSiteIncompatibleSupportDefaultCookieSerializer.setUseSecureCookie(useSecureCookie);
}
@Override
public void setUseHttpOnlyCookie(boolean useHttpOnlyCookie) {
super.setUseHttpOnlyCookie(useHttpOnlyCookie);
sameSiteIncompatibleSupportDefaultCookieSerializer.setUseHttpOnlyCookie(useHttpOnlyCookie);
}
@Override
public void setCookiePath(String cookiePath) {
super.setCookiePath(cookiePath);
sameSiteIncompatibleSupportDefaultCookieSerializer.setCookiePath(cookiePath);
}
@Override
public void setCookieName(String cookieName) {
super.setCookieName(cookieName);
sameSiteIncompatibleSupportDefaultCookieSerializer.setCookieName(cookieName);
}
@Override
public void setCookieMaxAge(int cookieMaxAge) {
super.setCookieMaxAge(cookieMaxAge);
sameSiteIncompatibleSupportDefaultCookieSerializer.setCookieMaxAge(cookieMaxAge);
}
@Override
public void setDomainName(String domainName) {
super.setDomainName(domainName);
sameSiteIncompatibleSupportDefaultCookieSerializer.setDomainName(domainName);
}
@Override
public void setDomainNamePattern(String domainNamePattern) {
super.setDomainNamePattern(domainNamePattern);
sameSiteIncompatibleSupportDefaultCookieSerializer.setDomainNamePattern(domainNamePattern);
}
@Override
public void setJvmRoute(String jvmRoute) {
super.setJvmRoute(jvmRoute);
sameSiteIncompatibleSupportDefaultCookieSerializer.setJvmRoute(jvmRoute);
}
@Override
public void setUseBase64Encoding(boolean useBase64Encoding) {
super.setUseBase64Encoding(useBase64Encoding);
sameSiteIncompatibleSupportDefaultCookieSerializer.setUseBase64Encoding(useBase64Encoding);
}
@Override
public void setRememberMeRequestAttribute(String rememberMeRequestAttribute) {
super.setRememberMeRequestAttribute(rememberMeRequestAttribute);
sameSiteIncompatibleSupportDefaultCookieSerializer.setRememberMeRequestAttribute(rememberMeRequestAttribute);
}
@Override
public void setSameSite(String sameSite) {
super.setSameSite(sameSite);
sameSiteIncompatibleSupportDefaultCookieSerializer.setSameSite(sameSite);
// SameSiteがNoneの場合は非互換ブラウザ向けのDefaultCookieSerializerにSameSiteにnullをセットする
if ("None".equals(sameSite)) {
sameSiteIncompatibleSupportDefaultCookieSerializer.setSameSite(null);
}
}
}