SpringSessionを使っているときにCookieをセキュアにする


Cookieのセキュリティ対策とは

SpringでWebシステムを作っていてCookieのセキュリティ対策ということで以下の3点をしたかったのです。

  • Secureモードにする
  • httponlyにする
  • セッションIDをクエリパラメータ(URLの一部)にしない

こちらのQiita記事にまとめてくださっていましたので結論だけ引用させて頂いて、詳しくはリンク先を、ということで。
SpringBootでCookie設定をする

Configクラス
    @Bean
    public ServletContextInitializer servletContextInitializer(@Value("${secure.cookie}")boolean secure) {

        ServletContextInitializer servletContextInitializer = new ServletContextInitializer() {
            @Override
            public void onStartup(ServletContext servletContext) throws ServletException {
                servletContext.getSessionCookieConfig().setHttpOnly(true);
                servletContext.getSessionCookieConfig().setSecure(secure);
                servletContext.setSessionTrackingModes(
                        Collections.singleton(SessionTrackingMode.COOKIE)
                );
            }
        };
        return servletContextInitializer;
    }

これで完了!なのですが、どうもうまくいかない・・・と思ったら、SpringSessionを使っている場合はちょっと変わります。

SpringSessionを使う場合はCookieの設定はCookieSerializerに書く

こんなドキュメントにたどり着きました。
Spring Session - Custom Cookie
http://docs.spring.io/spring-session/docs/current/reference/html5/guides/custom-cookie.html

Spring Session - Custom Cookie(2018/3/20 リンク修正)

セッション情報はSpringSessionに任せてるんだからCookieの設定もSpringSessionへ・・・
というわけで、私の場合はRedisを使うためにSessionConfigというクラスを作っていたのでこんな感じに。

SessionConfig.java
@EnableRedisHttpSession
@Configuration
public class SessionConfig {
    @Bean
    public CookieSerializer cookieSerializer(){
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();

        // HTTPSで動作している場合はHTTPSでないとCookieを返さないようにする
        serializer.setUseSecureCookie(true);

        // CookieへのアクセスはHTTPプロトコルに限定(SSHとかはNG)
        serializer.setUseHttpOnlyCookie(true);

        return serializer;
    }
}

@EnableRedisHttpSessionのあるクラスじゃないとダメ?⇒試しましたが別のクラスでも大丈夫。)
これでHTTPSでないとCookieを返さないセキュアモード、HTTPプロトコル以外はCookieにアクセスさせないHttpOnlyの設定ができました!

ローカルデバッグ用の設定を追加

めでたしめでたしなのですが、HTTPSでないとCookie返さないということは、ローカルデデバッグできなくなってしまうわけで。
これでは面倒なので判定を入れておきましょう。
環境変数やSpringProfileで判定してももちろんいいのですが、今回はapplication.ymlに設定を追加することにしました。

application.yml
syukai.security:
  useHttps: true
SecurityConfig.java
@Configuration
@EnableWebSecurity
@ConfigurationProperties("syukai.security")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private Boolean useHttps;
    public Boolean getUseHttps(){return this.useHttps;}
    public void setUseHttps(Boolean usehttps){this.useHttps=usehttps;}

//※もちろんこれをSessionConfigに書いてもいいのですが、セキュリティ関係の設定ということでこちらにしました。
}
SessionConfig.java
@EnableRedisHttpSession
@Configuration
public class SessionConfig {
    @Autowired
    SecurityConfig securityConfig;

    @Bean
    public CookieSerializer cookieSerializer(){
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();

        // HTTPSで動作している場合はHTTPSでないとCookieを返さないようにする
        if(securityConfig.getUseHttps()){
            serializer.setUseSecureCookie(true);
        }

        // CookieへのアクセスはHTTPプロトコルに限定(SSHとかはNG)
        serializer.setUseHttpOnlyCookie(true);

        return serializer;
    }
}

Cookie名を変更する

デフォルトのCookie名だとよろしくない・・・というセキュリティ対策というよりは、実はIE・Edge対策でCookie名を変更しました。

というのも、IE、EdgeではルートドメインのCookie名とサブドメインのCookie名を混同してくださるそうでして、詳しくは以下のリンク先記事をご参照ください。(ルートドメインに置くなよ、というのはさておき)

WEBマスターの知恵ブログ - 【ie】サブドメインとセッション・クッキーの不具合【PHP】

記事ではie9のときのことが書かれていましたが、IE11、Edgeでも同様でした。
ですので、こんな感じでルートドメインとサブドメインでCookie名を分けておきます。

SessionConfig.java-CookieSerializer
    if(isRootDomain()){
        serializer.setCookieName("ROOTSESSION");
    }else{
        serializer.setCookieName("SUBSESSION");
    }

isRootDomainの中身は先ほどと同じく環境変数なりSpringProfileなりapplication.ymlの値なりで判定します。

以上!