CSRFトークンの仕組みと動作をわかりやすく解説!Spring Securityの基本
新人
「Spring Securityでよく聞くCSRFトークンって、何のためにあるんですか?」
先輩
「それはWebアプリを守るためのセキュリティ機能だよ。CSRFトークンはクロスサイトリクエストフォージェリ対策として使われるんだ。」
新人
「クロスサイト……リクエストフォージェリ?なんだか難しそうですね……」
先輩
「安心して。今回はCSRFの仕組みからCSRFトークンの意味まで、初心者でもわかるようにゆっくり説明していくよ。」
1. CSRFとは何か?(クロスサイトリクエストフォージェリの意味)
CSRF(シーエスアールエフ)は、「クロスサイトリクエストフォージェリ(Cross-Site Request Forgery)」の略で、ユーザーが意図しない操作を実行させる攻撃手法のことを指します。
たとえば、ユーザーがログイン中の銀行サイトを開いたまま、別の悪意あるWebサイトにアクセスしてしまった場合、攻撃者はそのユーザーのセッションを悪用して、銀行サイトに振込リクエストを送ることができます。
このようにして、ユーザー本人の意思とは関係なく、勝手に操作が実行されることがあります。CSRF攻撃は、特に認証済みセッションを持つWebアプリケーションにとって重大な脅威となります。
Spring Securityでは、こうした攻撃を防ぐためにCSRF対策が初期設定で有効になっています。つまり、開発者が特別な設定をしなくても、最低限の防御機能が備わっているということです。
2. CSRFトークンとは何か?どんな目的で使うのか
CSRFトークンは、CSRF攻撃を防ぐためにサーバーが発行する一意の値です。Spring Securityでは、ユーザーのセッションごとにこのトークンを生成し、フォームの中に含めて送信することで、「正当なリクエストかどうか」を判定します。
つまり、CSRFトークンがリクエストに含まれていなければ、Spring Securityはそのリクエストを拒否するのです。
CSRFトークンの目的は、攻撃者が作成した偽のフォームやリクエストには正しいトークンが含まれていないため、それを検出して拒否することにあります。
実際に、Spring Securityでは以下のようにしてフォーム内にトークンを埋め込むことが推奨されています。これはThymeleafを使わず、プレーンなHTMLで記述した例です。
@Controller
public class FormController {
@GetMapping("/input")
public String showForm(Model model, CsrfToken csrfToken) {
model.addAttribute("_csrf", csrfToken);
return "inputForm";
}
}
<form method="post" action="/submit">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<label>ユーザー名:</label>
<input type="text" name="username" />
<button type="submit">送信</button>
</form>
このように、HTMLフォームにCSRFトークンを埋め込むことで、Spring Securityはその値をチェックし、正しいセッションと一致しているかを自動的に検証します。
CSRFトークンは毎回変更されるわけではありませんが、セッション単位で固有のトークンが保持されており、予測困難なランダムな値として扱われます。
このトークンを知らない第三者がリクエストを偽装しても、正しいトークンを含めることは不可能であるため、CSRFの仕組みにより攻撃は無効化されます。
CSRFトークンは、ログイン後のフォーム送信・データ更新・パスワード変更など、重要なアクションを保護するためのカギとなります。
Spring SecurityのCSRF制御機能は、このような攻撃を自動的に防ぐ設計になっているため、CSRFトークンの使い方を理解しておくことは安全なアプリ開発の第一歩です。
3. Spring SecurityにおけるCSRFトークンの生成タイミングと仕組み
Spring Securityでは、HTTPリクエストが初めてセッションを確立した時点で、CSRFトークンが自動的に生成されます。このトークンはセッションにひもづけられており、セッションが維持されている限り再利用されます。
つまり、ログイン後や最初のアクセス時にセッションが作られると、その時点でサーバー側にCSRFトークンが作成されるという流れです。
トークンの生成にはCsrfTokenRepositoryという仕組みが使われ、通常はHttpSessionCsrfTokenRepositoryが利用されます。これはセッションの中にトークンを保存する方式です。
たとえば、以下のようにSecurityConfigで明示的に指定することもできます。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(new HttpSessionCsrfTokenRepository())
)
.authorizeHttpRequests(authz -> authz
.anyRequest().permitAll()
);
return http.build();
}
}
CSRFトークンの生成タイミングを理解しておくことで、どのタイミングでトークンを取得し、フォームに埋め込むべきかが明確になります。
また、CookieCsrfTokenRepositoryという方法もあり、トークンをCookieとしてクライアントに送信し、JavaScriptから取得して使用することも可能ですが、初心者のうちはHttpSession方式の理解を優先するのがおすすめです。
4. CSRFトークンの検証方法(サーバー側で何が行われているのか)
Spring Securityでは、CSRFトークンが含まれるリクエストを受け取った際、自動的にそのトークンが正しいかどうかをサーバー側で検証します。これは、セッションに保存されているトークンと、リクエストに含まれているトークンの値を比較するという仕組みです。
CSRFトークン検証の流れは以下のとおりです:
- ユーザーがフォームを開くと、セッションとともにCSRFトークンが発行される
- HTMLフォーム内にトークンがhidden項目として埋め込まれる
- ユーザーがフォームを送信すると、トークンも一緒に送信される
- Spring Securityは受け取ったリクエストの中からトークンを読み取り、セッションのトークンと比較する
- トークンが一致すれば、正当なリクエストとみなされ処理が続行される
この仕組みによって、外部のサイトから送られた不正なPOSTリクエストや、悪意のあるJavaScriptからの操作はSpring Security CSRF制御によってブロックされます。
開発者は、この検証を手動で実装する必要はなく、Spring Securityがすべて自動で処理してくれます。重要なのは、HTMLフォームやJavaScriptからのリクエストにトークンを正しく含めることです。
Thymeleafを使っていない場合でも、Javaの@ControllerでCsrfTokenをモデルに追加し、HTMLに埋め込む方法がよく使われます。
@Controller
public class SampleController {
@GetMapping("/form")
public String form(Model model, CsrfToken token) {
model.addAttribute("_csrf", token);
return "form";
}
}
フォーム側のHTMLでは以下のようにhidden項目にして送信します。
<form method="post" action="/submit">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<input type="text" name="name" />
<button type="submit">送信</button>
</form>
このようにCSRFトークン検証は、サーバーがセッションと比較して自動で実行するため、正しく仕組みを理解してHTML側に組み込むことが最も大切です。
5. トークンが一致しない場合の挙動(403 Forbiddenなど)
CSRFトークンが一致しない、もしくはトークンがまったく含まれていない場合、Spring Securityはそのリクエストを403 Forbiddenとして拒否します。
これは、攻撃者による不正なリクエストである可能性が高いと判断されるからです。たとえば、次のようなケースが考えられます:
- HTMLフォームにトークンを埋め込むのを忘れた
- トークンの名前や値が誤っている
- JavaScriptで送る際、リクエストヘッダーにトークンを含めていない
- セッションがタイムアウトして、トークンが無効になっている
このような場合、Spring Securityは即座にリクエストを打ち切り、ユーザーには403エラーが表示されます。
この挙動は非常に重要で、セキュリティ上の防壁として機能するCSRFトークンの仕組みが正しく働いている証拠です。
開発中に403エラーが出た場合は、焦らず以下の点をチェックしてみましょう。
- フォームに
input hiddenでトークンが含まれているか? - トークンの
nameとvalueが正しく設定されているか? - セッションが有効か?(再ログインなどで確認)
- JavaScriptでリクエストする場合はヘッダーにトークンを追加しているか?
Spring Securityでは、これらのミスを検出してCSRFトークンの検証に失敗した際に自動で403エラーを返してくれるため、安全なWebアプリ開発の助けになります。
逆に言えば、この403エラーが出るということは、CSRF対策が正しく機能している証拠でもあるため、慌てずに原因を確認し、適切な修正を行いましょう。
6. HTMLフォームへのCSRFトークンの埋め込み方法(Thymeleafなしで)
Spring Securityでは、Thymeleafテンプレートを使わなくても、通常のHTMLファイルにCSRFトークンを埋め込むことができます。これは、静的なHTMLファイルでもCSRFトークンの仕組みを活用できるということです。
以下のように@Controllerクラスでトークンを取得し、Modelに設定してからHTMLに渡すのが一般的な方法です。
@Controller
public class SimpleFormController {
@GetMapping("/register")
public String registerForm(Model model, CsrfToken csrfToken) {
model.addAttribute("_csrf", csrfToken);
return "register";
}
}
HTML側では以下のように、input type="hidden"でトークンを埋め込みます。変数はJSPやFreeMarkerのような式展開で出力できます。
<form method="post" action="/register">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<label>名前:</label>
<input type="text" name="name" />
<button type="submit">登録</button>
</form>
このようにすれば、Thymeleafを使わない環境でもCSRFトークンの埋め込みが可能です。トークンの名前や値はSpring Securityが提供するCsrfTokenオブジェクトから動的に取得されるため、固定値を埋め込む必要はありません。
この方法は、テンプレートエンジンを導入していないシンプルな構成のアプリケーションでも、安全なフォーム実装ができるため、初心者にとって理解しやすく、実用的です。
7. Javaコードでのトークン取得とフォーム連携方法
CSRFトークンをJavaコードで扱う方法は非常に簡単です。Spring Securityでは、CsrfTokenというオブジェクトを自動で生成し、コントローラメソッドに引数として渡すことができます。
次の例では、/user/formという画面にアクセスしたときに、サーバー側でトークンを取得してビューに渡す処理を行っています。
@Controller
public class UserFormController {
@GetMapping("/user/form")
public String showForm(Model model, CsrfToken csrfToken) {
model.addAttribute("_csrf", csrfToken);
return "userForm";
}
}
このようにModelにトークンを追加しておけば、HTML側でそれを表示できます。以下は対応するHTMLコードの一部です。
<form method="post" action="/user/create">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<label>メールアドレス:</label>
<input type="email" name="email" />
<button type="submit">作成</button>
</form>
このようにしておけば、ユーザーがフォームを送信する際に自動的にCSRFトークンも一緒に送信されます。サーバー側では、リクエストに含まれるトークンとセッション内のトークンを比較して、安全性を検証してくれます。
また、CSRF除外設定を行っていない限り、Spring Securityはこのトークンの一致を必ずチェックするため、安全性を損なうことはありません。
このようにJavaコードとHTMLを連携させることで、Spring Security フォーム CSRFの基本的な仕組みを理解し、実装することができます。
8. 開発と本番での安全なCSRF運用の注意点
Spring SecurityにおけるCSRFトークンの仕組みは非常に強力ですが、開発と本番の環境で正しく扱うためには、いくつかの注意点があります。
まず、開発中によくあるのが、CSRF除外設定を一時的に有効にしてしまうことです。たとえば、以下のようなコードでCSRFを無効にしてしまうと、テスト中は動いても本番で攻撃を受けやすくなります。
@Configuration
@EnableWebSecurity
public class UnsafeConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable());
return http.build();
}
}
このようなcsrf.disable()の設定は、特別な理由がない限り使用すべきではありません。開発中でも403 Forbiddenが出た場合は、前述したように正しくトークンを埋め込むことで解決しましょう。
次に、本番環境でCookieCsrfTokenRepositoryを使う場合の注意点です。JavaScriptを使ったSPAやAjaxリクエストでトークンを扱うには、この方式が向いていますが、セキュリティヘッダーやCORSの設定を適切に行わないと、逆に情報漏洩のリスクがあります。
そのため、初心者のうちはHttpSessionCsrfTokenRepositoryを使ったトークン管理を基本とし、まずはフォームからのPOST処理で確実にCSRFを防ぐ構成にしておくのが安全です。
さらに、セッションのタイムアウトやトークンの有効期限にも注意が必要です。セッションが切れると、CSRFトークンも使えなくなるため、ログイン後に時間が経過した場合などは、再ログインや画面の再読み込みを促す設計が推奨されます。
まとめると、以下のポイントを意識することが重要です:
- CSRFトークンを無効化せず、開発中から正しい実装で慣れる
- テンプレートエンジンなしでも安全にトークンを埋め込む
- セッション方式でのトークン管理に慣れる
- 本番ではCookie利用やCORS設定などに注意
このようにして、CSRFの仕組みを理解したうえで、安全なフォーム処理を設計・実装することで、攻撃を未然に防ぐことができます。