Grails3.2でのCSRF対策
CSRF対策を導入
標準だとGSPのフォームで利用する2重投稿防止用の<g:form useToken="true" ...>
を使うことでCSRF対策が出来る。
ただしこの場合、当然コントローラ側でトークンが正しいかどうかのチェックをしないといけないので、
withForm { // OK! }.invalidToken { // トークンがおかしい! }
というコードを必要な箇所に全て記述しなければならない。
シンプルなコードとは言え、さすがに全コントローラにこのような記述を毎回記述するのは大変。
ということで、基本的にはSpringSecurityCoreを利用するのが楽。
SpringSecurityCoreでの設定
resource.groovy
に以下を追記。
csrfFilter(CsrfFilter, new HttpSessionCsrfTokenRepository()) { // 振る舞いは自分でクラスを用意することで、以下のようにして変更可能 // accessDeniedHandler = ref('自作AccessDeniedHandler') // requireCsrfProtectionMatcher = ref('自作RequireCsrfProtectionMatcher') }
Bootstrap.groovy
に以下を追加
SpringSecurityUtils.clientRegisterFilter('csrfFilter', SecurityFilterPosition.LAST.order + 10)
基本的な設定はこの2つのみ。
クライアント側の設定
で、基本的なFORMからのPOSTとかの場合は、
<input type="hidden" name="_csrf" value="${_csrf.token}">
を付けるだけでもう完了。
これで後はSpringSecurityCoreが全て全自動でチェックしてくれる。わざわざ自分でコントローラ側にトークンの妥当性チェックを書く必要をない。
ただし、JavaScriptからajaxでデータを投げる場合にはHTTPヘッダーに埋め込む方が良いらしい。
なので、基本的には全ページのheadタグ内に以下の内容を追記。
<meta name="_csrf" content="${_csrf?.token}"/> <meta name="_csrf_header" content="${_csrf?.headerName}"/>
で、実際にサーバにデータを投げるJavaScriptで、上記の2つのデータを元にHTTPヘッダーを生成すればOK。
以下は、自分が今作っているアプリケーションで、jQueryでmetaタグの情報を取得して、axiosで実際のそのヘッダーと共にデータをサーバに投げるサンプル。
// metaタグから値を取得 const csrfHeader = $("meta[name='_csrf_header']").attr("content"); const csrfToken = $("meta[name='_csrf']").attr("content"); const headers = {}; headers[csrfHeader] = csrfToken; axios.post( '/u/r/l', { // サーバに投げるデータを指定 name: 'grails' version: '3.2.9' }, { // コレがそのヘッダー headers: headers } ) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
(ちなみにヘッダー名はデフォルトではX-CSRF-TOKEN
)
なお、Bootstrapに追加したコードの詳細は現在調査中。
もしこれを省いた場合、別のサイトから未ログイン状態のユーザが悪意を持ってPOSTとかPUT、DELETEで遷移させられてきた場合には、まずログイン画面が表示される。
ユーザがもし疑問に思わずにログインすると、GET扱いでログイン先のページに移ってしまう。
(GETになるのでPOSTとかで送られたパラメタはnullになる為、大抵データを保存するような場面なのでvalidationエラーになるはず。だが、それは本来意図したエラーチェックではない)
上記のBootstrapの1行を追加すればちゃんとSpringSecurityCoreがエラー扱いにしてくれる(Statusコードが403じゃなくて999になるので本当に意図したエラーなのか不安。。。ここの辺りを現在調査中)
あと、Bootstrapの1行を追加するとログインフォームにもTokenが必要になるので、views/login/auth.gpsとdenied.gsp
をプラグインからコピーしてきて修正する。
参考ページ
17 Security
8.1.11 Handling Duplicate Form Submissions
Grails 3 CSRF protection
6.7.2.3. AjaxによるCSRFトークンの送信
Spring Security Core Plugin - Reference Documentation