上記組み合わせでの情報が少なかったのでまとめました。
対策方針
CSRF(クロスサイトリクエスト偽造)対策としては、一般的なトークン発行方式としています。
サーバ→クライアントへのトークン情報の通知はSet-Cookieを利用するのが楽ですね。Spring Bootがやってくれるので。1
クライアント→サーバへのトークン送信はJavaScriptで独自に書いて、GETリクエスト以外2にCookie情報をhttpヘッダに書かせます。
- Spring Boot
1.1. CSRFのトークンの保存先をCookieとする。
1.2. JSからのアクセスを可能とするためにHttpOnlyとしない。 - Ext JS
2.1. GETリクエストは対象外とする。
2.1. ExtでのAjaxリクエスト送信時にCookie値からトークン情報を取得し、Httpヘッダに付与する。
実装例
Spring Boot
デフォルトでCSRF対策は有効ですが、トークンの保存先指定と、httpOnlyの無効化の定義をしています。
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
...// 後略。適宜必要な定義を。
}
}
Ext JS
Ajaxで飛ばす全てのリクエストにトークン付与が必要なので、上位に呼ばれる箇所で定義をしています。
"beforerequest"として定義することで、Ajaxリクエスト発行前に処理が行われるようにして、個別に実装しなくて良いようにしています。
Ext.Ajax.on('beforerequest', function (conn, request, eOpts) {
// GETリクエスト以外の場合にCSRFトークンを付与する。
if (request.method === 'GET') {
return;
}
// CookieのXSRF-TOKEN項目値を、任意httpヘッダX-XSRF-TOKENとして付与する。
if (document.cookie) {
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var str = cookies[i].split("=");
if (str[0] == 'XSRF-TOKEN') {
conn.setDefaultHeaders({ 'X-XSRF-TOKEN': unescape(str[1]) });
break;
}
}
}
});
-
(最初勘違いして)Cookieでトークン渡しても、セッションIDをCookieで持たせてる場合と同じでブラウザが勝手に送信してしまうので、CSRF対策として意味ないのでは?と思ったのですが、Spring BootでのCSRFトークンのチェックはCookie値を見ているのではなく、「formデータの_csrf」または「httpヘッダのX-XSRF-TOKEN」を見ているのがミソでした。トークンをCookieからformデータかhttpヘッダに置き換える必要がありますが、これをクロスオリジンなJSではできない(正規ドメインのCookieを見れない)ので対策となっているわけです。 ↩
-
CSRF攻撃の特性を考えるとGETリクエストでは検証する必要ないですね。Spring Bootではサーバサイドチェックをしていません。 ↩