この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

TomcatでCookieのSameSite属性を無理やり付与する

最近のChromeFirefoxでは、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();
            }
        });
    }
}