SpringBoot+SpringSessionでSamesite Cookie対応

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);
		}
	}
}
```