最近のChromeやFirefoxでは、CookieにSameSiteという属性を指定することができます。 これは、CSRFから保護する強力な仕組みであるようです。
簡単に言うと、外部ページから内部ページへ直接リクエストを飛ばしても(※CSRFの動作原理)、Cookieヘッダを送信しないようにできる仕組みです。
詳しくはこちら。
http://qiita.com/flano_yuki/items/b87b2c28db0b056665ef http://scotthelme.co.uk/csrf-is-dead/
ぜひ使いたい属性なのですが、Servlet APIは未対応です。困る。 というわけでどうにかしてみます。
TomcatでSameSiteを付ける(ソフト編)
TomcatにはCookieProcessorという仕組みがあります。
http://tomcat.apache.org/tomcat-8.5-doc/config/cookie-processor.html
このCookieProcessorが、CookieオブジェクトをSet-Cookieヘッダに変換する役目を果たしています。 任意のCookieProcessor実装を指定することが可能です。
例えば、
package foo; import javax.servlet.http.Cookie; import org.apache.tomcat.util.http.Rfc6265CookieProcessor; public class MyCookieProcessor extends Rfc6265CookieProcessor { @Override public String generateHeader(final Cookie cookie) { return super.generateHeader(cookie) + "; SameSite=Strict"; } }
というクラスを作って、context.xmlで
<CookieProcessor className="foo.MyCookieProcessor" />
と指定すればOK。
TomcatでSameSiteを付ける(ハードコア編)
先程はオフィシャルな手段でCookieProcessorの実装を切り替えましたが、 僕は事情があってcontext.xmlを変更できないため、Javaのコードだけで完結させる必要がありました。
で、調べてみた所、以下のやり方で動くようです。 リフレクションで無理やりTomcatの内部クラスを操作しています。
package foo; import java.lang.reflect.Field; import java.nio.charset.Charset; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.Cookie; import org.apache.catalina.Context; import org.apache.catalina.core.ApplicationSessionCookieConfig; import org.apache.tomcat.util.http.CookieProcessor; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.http.ServerCookies; public class EnforceSameSiteAttr implements ServletContextListener { @Override public void contextDestroyed(final ServletContextEvent sce) {} @Override public void contextInitialized(final ServletContextEvent sce) { final ApplicationSessionCookieConfig conf = (ApplicationSessionCookieConfig) sce .getServletContext() .getSessionCookieConfig(); final CookieProcessor cookieProc; final Context context; try { final Field f = ApplicationSessionCookieConfig.class .getDeclaredField("context"); f.setAccessible(true); context = (Context) f.get(conf); cookieProc = context.getCookieProcessor(); } catch (final Exception e) { throw new Error(e); } context.setCookieProcessor(new CookieProcessor() { @Override public void parseCookieHeader( final MimeHeaders headers, final ServerCookies serverCookies) { cookieProc.parseCookieHeader(headers, serverCookies); } @Override public String generateHeader(final Cookie cookie) { return cookieProc.generateHeader(cookie) + "; SameSite=Strict"; } @Override public Charset getCharset() { return cookieProc.getCharset(); } }); } }