Spring Security Javascript APIのCSRF対応

Javascript

Spring Securityを導入したWebアプリのAPIをJavascriptから利用する場合、CSRFトークンを取得して一緒に送らないと403エラーが発生します。
この記事では、JavascriptからSpring BootのAPIを利用する際にCSRFトークンを取得する方法を解説します。

JavaLearning

Spring Boot側の準備

Spring Boot側のコントローラークラスにCSRFトークンを取得するための入り口を用意します。
これをJavascriptから呼び出した時に、CSRFトークンを取得できます。

package com.example;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GetTokenController {
    
    @GetMapping(value="/csrftoken")
    public String getCsrfToken(HttpServletRequest request) {

        DefaultCsrfToken token = (DefaultCsrfToken) request.getAttribute("_csrf");
        if (token == null) {

            throw new RuntimeException("could not get a token.");
        }

        return token.getToken();
    }
}

JavascriptからAPIを利用する

Javascriptから、まずCSRFトークンを取得します。
先にCSRFトークンを取得し、取得したCSRFトークンをヘッダの「X-XSRF-TOKEN」に格納してAPIを利用します。
順番として、CSRFトークンを取得し終わってからAPIを利用できるように、awaitをつけてfetchを呼び出します。

async function executeApi() {
    let csrfToken = "";
    await fetch("https://example.com/csrftoken", {
        method:'GET'
    })
    .then(res => res.text())
    .then(str => {
        csrfToken = str;
    });

    let form = new FormData();
    form.append("Data", "StringData");

    let response = "";
    await fetch("https://example.com/api", {
        method: 'POST'
    ,   body: form
    ,   headers: {
            'X-XSRF-TOKEN' : csrfToken
        }
    })
    .then(response =>  response.text())
    .then(data => {
        response = data;
    });

    console.log(response);
}

セキュリティの設定

CSRFトークンを取得するページは、ログインしていなくてもアクセスができるように設定しておく必要があります。

package com.example;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
            .authorizeRequests(authorize -> authorize
                // ログイン認証しなくてもアクセスできる
                .antMatchers("/csrftoken").permitAll()
                .anyRequest().authenticated()
            );
    }
}

まとめ

CSRFトークンを取得する準備をしたり、トークンを取得してからAPIを呼び出したり、いくつかの段階を踏む感じです。
そういうと面倒そうですが、一度準備してしまえばあとは使うだけです。
作ったあとは何度も使うことになると思うので、引数を渡してパーツとして使えるように作成して修正するときに面倒にならないように作成するのがおすすめです。

それでは〜。

コメント

タイトルとURLをコピーしました