Spring Security | RestAPIでCSRFトークンを利用する
2022.08.16SpringSecuritは、CSRFトークンをフォームに対してhidden項目としてCsrfRequestDataValueProcessorが自動的に埋め込んでいます。関連記事:https://volkruss.com/?p=3125
これはこれでいいのですが、RESTAPIでCSRFトークンを利用するにはどうすればいいのか。ということでやってみます。
CSRFトークンを利用する
https://www.baeldung.com/csrf-stateless-rest-api
上記のサイトに答えが書いてありますが、Securityの設定時にcsrfTokenRepositoryを利用するだけです。
以下のような設定クラスを作成して試してみます。
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
http.authorizeRequests(auth -> {
auth.antMatchers("/**").permitAll();
});
return http.build();
}
}
PostmanからGET送信をするとクッキーにCSRFトークンが設定されているのがわかります
この値をHeadersに付与してPOST送信をしてみます。SpringはX-XSRF-TOKENヘッダで受け取るようになっています。
正しくPOSTメソッドが利用できています。
次にこのトークンを削除してPOST送信を行います
しっかり403エラーになっていることがわかります。
CORSの設定
jsからの利用をしてみるためcorsの設定をしておきます。今回は3000番ポートからリクエストを送信します
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
http.authorizeRequests(auth -> {
auth.antMatchers("/**").permitAll();
});
// 追加
http.cors().configurationSource(corsConfigurationSource());
return http.build();
}
// CORSの設定
@Bean
public CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration cors = new CorsConfiguration();
cors.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
cors.setAllowedMethods(Arrays.asList("GET","POST"));
cors.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",cors);
return source;
}
}
configurationSourceを設定してあげればOKです
JavaScriptから利用する
ボタンを二つ用意してGETとPOSTを行えるようにします。
- GETボタン
- CookieにCSRFトークンを受け取る
- POSTボタン
- CSRFトークンを利用する
htmlとかは適当に書いてます
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="get">GET</button>
<button id="post">POST</button>
<hr>
<script>
const getBtn = document.getElementById("get");
getBtn.addEventListener('click',doget);
const postBtn = document.getElementById("post");
postBtn.addEventListener('click',dopost);
function doget(){
fetch('http://localhost:8080/get',
{
method:'GET',
credentials: 'include' // これを付ければcookieを受け取れます
})
.then(res => res.text())
.then(str => console.log(str))
}
function dopost(){
const csrfToken = document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1');
console.log(csrfToken);
fetch('http://localhost:8080/post',{
method:'POST',
credentials: 'include', // ここでも必要
headers: {
'X-XSRF-TOKEN' : csrfToken // cookieから取得したcsrfトークンを設定する
},
})
.then(res => res.text())
.then(str => console.log(str))
}
</script>
</body>
</html>
- csrfTokenの値は参考サイトの通りにcookieから取得しています
- postmanでも試したようにcsrfトークンは”X-XSRF-TOKEN”というkey名で送信します
- credentials: ‘include’がないとcookieを受け取れないし、ヘッダーに付与もできません
- https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
GETボタンを押してもCORSエラーになってしまいます
Access to fetch at 'http://localhost:8080/get' from origin 'http://localhost:3000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.Spring側でもCORSの設定でsetAllowCredentialsが必要になります。
// CORSの設定
@Bean
public CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration cors = new CorsConfiguration();
cors.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
cors.setAllowedMethods(Arrays.asList("GET","POST"));
cors.setAllowedHeaders(Arrays.asList("*"));
// cookieなどの情報を許可
cors.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",cors);
return source;
}
これでOKです。GETとPOSTが問題なく動いてることが確認できます。
- https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/CorsConfiguration.html#setAllowCredentials-java.lang.Boolean-
cookieの値が取得できて、無事に疎通もできていることが確認できました。これでRestAPIでもcorsを無効化せずに利用できます。
参考
https://www.baeldung.com/csrf-stateless-rest-api
https://spring.pleiades.io/spring-security/reference/6.0/servlet/integrations/cors.html