CSRF対策が有効なフォームの作成方法を完全解説!Spring Securityの仕組みと設定ポイント
新人
「Spring SecurityのCSRF対策って何のためにあるんですか?フォームに何か追加しないとダメなんですか?」
先輩
「うん、フォームにCSRFトークンを追加しないと、Spring Securityがリクエストを受け付けてくれないよ。セキュリティ上の対策なんだ。」
新人
「フォームのセキュリティって言われてもピンと来ません…。具体的にどんな仕組みなんでしょうか?」
先輩
「じゃあまずは、CSRF攻撃が何かから順に説明していこうか。」
1. CSRFとは?(クロスサイトリクエストフォージェリの意味と脅威)
CSRF(シーエスアールエフ)とは、クロスサイトリクエストフォージェリと呼ばれるセキュリティ上の攻撃手法のことです。日本語では「サイト間リクエスト偽造」とも訳されます。
この攻撃は、ログイン中のユーザーが知らないうちに、意図しない操作をWebアプリケーションに対して実行させられるというものです。
たとえば、ユーザーが銀行の振込画面にログインしている状態で、別の悪意のあるサイトを開いてしまったとします。すると、その悪意あるサイトが自動で振込リクエストを送ってしまい、ユーザーが気づかないうちにお金が送金されるという危険性があります。
このように、セッションを利用したフォーム操作やアカウント更新処理などは、攻撃対象になりやすいのです。
そこで必要になるのがCSRF対策です。Spring Securityではこのような攻撃から守るために、デフォルトでCSRF保護が有効になっています。
フォームのセキュリティを確保するためには、正しいCSRFトークンを埋め込んだリクエストだけを受け付けるようにすることが重要です。
2. Spring SecurityにおけるCSRF保護の基本設定と自動有効化の仕組み
Spring Securityでは、CSRF対策が初期設定で有効になっており、開発者が特別な設定をしなくても、自動的に保護がかかるようになっています。
具体的には、POST・PUT・DELETEなどの変更系リクエストに対して、CSRFトークンの検証が行われるようになっています。
Spring Securityの内部では、HttpSecurityの中で以下のような設定が自動で適用されています。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.enable()) // デフォルトで有効なので明示的な記述は不要
.authorizeHttpRequests(authz -> authz
.anyRequest().permitAll()
);
return http.build();
}
}
このように、特別な設定をしなくてもCSRF対策は機能しています。
ただし、フォームの送信時にはCSRFトークンを含めなければ、Spring Securityが403 Forbiddenを返すようになっています。
そのため、開発者はフォームの中にCSRFトークンをhidden項目として埋め込む必要があります。
このとき、Thymeleafを使用していない場合でも、Javaの@Controllerでトークンを取得し、HTMLに渡すことで対応が可能です。
以下は、pleiades + Gradleの開発環境で構築したSpringプロジェクトで、CSRFトークンをフォームに連携する方法の一部です。
@Controller
public class UserFormController {
@GetMapping("/form")
public String showForm(Model model, CsrfToken token) {
model.addAttribute("_csrf", token);
return "userForm";
}
}
このようにしてJava側でトークンをModelに渡し、HTMLフォームに埋め込むことで、Spring SecurityによるCSRF保護が有効な安全なフォームを作成することができます。
次回は、実際のHTMLコードでCSRFトークンを埋め込む方法や、セッションとの連携方法を解説していきます。
3. CSRFトークンの仕組みと発行タイミング(セッション連携)
Spring Securityでは、ユーザーがログインしてセッションを開始したタイミングで、自動的にCSRFトークンの生成が行われます。このトークンはセッションごとに割り当てられ、サーバー側で保持されます。
このトークンは、ユーザーがフォームページへアクセスした際にJavaの@Controller経由でHTML側に渡され、フォームにhidden項目として埋め込まれることで、リクエストの正当性を証明する役割を果たします。
つまり、サーバー側で管理しているCSRFトークンと、フォームから送信されたトークンが一致すればリクエストは成功し、一致しなければ403 Forbiddenが返されます。これがSpring Security フォーム CSRF対策の基本です。
このように、トークンの発行と検証にはセッション情報が連携しており、ログイン後のセッションに依存した保護が行われている点がポイントです。
4. CSRFトークンのHTMLフォームへの埋め込み(Thymeleafなし)
Thymeleafを使っていれば、簡単に<form:form>タグでCSRFトークンを自動埋め込みできますが、今回は使用しない前提です。
そのため、Javaの@Controllerから明示的にトークンを取得し、HTMLテンプレートでhidden項目として手動で記述する必要があります。
以下は、フォームにCSRFトークンを埋め込むHTMLコードの例です。
<form action="/submit" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<input type="text" name="username" />
<input type="submit" value="送信" />
</form>
ここで、${_csrf.parameterName}には_csrfオブジェクトのパラメータ名(通常は_csrf)、${_csrf.token}には実際のトークン値が格納されています。
これらは先ほどの@Controllerで次のように渡しています。
@GetMapping("/form")
public String showForm(Model model, CsrfToken token) {
model.addAttribute("_csrf", token);
return "userForm";
}
HTML側ではEL式(${ })でトークン値を参照し、明示的にフォームへ埋め込むことで、Spring Security フォーム CSRF対策が完成します。
5. トークンの検証処理と403 Forbiddenの理由
Spring Securityは、POSTやPUTなどのHTTPリクエストに対して、内部的にトークンの検証処理を行っています。
この検証は、リクエストヘッダーやフォーム内のhidden項目に含まれているトークンと、セッションに保存されているトークンを照合することで行われます。
照合に成功すればリクエストが通過し、失敗すると自動的に403 Forbiddenエラーが返されます。このエラーは、CSRFトークンが無効・未送信・不一致であることを示しています。
開発時によくある失敗例として、次のようなケースがあります:
- フォームに
<input type="hidden">でトークンを埋め込むのを忘れた @ControllerでCsrfTokenをModelに渡していない- HTML内で
${_csrf.token}の記述を間違えている - JavaScriptで動的に生成したフォームにトークンを入れていない
このような場合は、403 Forbiddenが表示され、リクエストがブロックされます。
Spring Securityは、セッションが保持されている限り、常にサーバー側にCSRFトークンを保存しており、リクエストごとのトークン照合が自動で実行されるようになっています。
したがって、CSRF対策が有効なフォームを作成するには、必ず以下のポイントを意識してください。
- トークンをHTMLに確実に埋め込むこと
- @Controllerでトークンを忘れずにModelに追加すること
- トークンの送信先が正しいエンドポイントであること
CSRFトークンの仕組みを理解し、正しくフォームに組み込むことで、403 Forbiddenエラーの回避と同時に、強固なセキュリティを実現できます。
6. Javaコードとフォーム連携による安全な送信処理
ここでは、CSRFトークンの仕組みを理解した上で、@ControllerとHTMLフォームを連携させた安全な送信処理を実装する方法を紹介します。
まず、@Controllerでトークンを取得してModelに渡します。そして、POSTリクエストを受け取るメソッドを用意して、フォームの送信処理を安全に行います。
@Controller
public class UserFormController {
@GetMapping("/form")
public String showForm(Model model, CsrfToken token) {
model.addAttribute("_csrf", token);
return "userForm";
}
@PostMapping("/submit")
public String submitForm(@RequestParam("username") String username, Model model) {
model.addAttribute("message", "ユーザー名:" + username + " を受け付けました。");
return "result";
}
}
次に、フォーム側のHTMLテンプレートには、次のようにCSRFトークンを埋め込みます。
<form action="/submit" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<label>ユーザー名:</label>
<input type="text" name="username" />
<input type="submit" value="送信" />
</form>
このように記述することで、Spring Security フォーム CSRF対策に準拠した安全な送信処理が可能となります。
7. CSRF除外設定のリスクと使い方(どうしても必要な場合の対応)
特定のエンドポイントでCSRFトークンを送らない必要があるケースもまれにあります。たとえば、外部APIからのPOSTリクエストやWebhook受信処理などです。
このような場合、Spring Security 設定方法として、CSRFトークン検証を一部のパスで除外する設定を行うことが可能です。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.ignoringRequestMatchers("/webhook/**") // 除外するパスを指定
)
.authorizeHttpRequests(authz -> authz
.anyRequest().permitAll()
);
return http.build();
}
}
このようにignoringRequestMatchersを使って、特定のURLパスに対するCSRF除外設定を行うことができます。
ただし、この設定には重大なリスクが伴います。CSRF保護を無効化したエンドポイントは、外部からのリクエストを無制限に受け入れてしまう可能性があります。
特に、認証済みのセッションと連携している処理で除外設定を行うと、セキュリティホールになる危険性が高いです。
このため、CSRFを除外する際は以下の対策を併用しましょう。
- トークン認証やAPIキーによる認可処理を追加する
- 除外対象のエンドポイントを限定的に絞り込む
- ログの出力や
IP制限を導入して不正アクセスを監視する
このように、CSRF除外設定は便利な反面、セキュリティ上の抜け道になりかねないため、最小限の使用に留めましょう。
8. 本番環境で安全に運用するためのベストプラクティス
最後に、本番環境でCSRF対策を含むセキュリティ運用を安全かつ安定して行うためのベストプラクティスを紹介します。
(1)セッショントークンのタイムアウトを設ける
CSRFトークンはセッションに紐づいているため、セッションが無効になるとトークンも無効になります。長時間放置されたセッションを自動で切断することで、不正利用を防止できます。
Spring Bootでは、application.propertiesなどで次のように設定します。
server.servlet.session.timeout=15m
これにより、最後のアクセスから15分が経過すると自動的にセッションが破棄されます。
(2)トークンのCookie管理との違いと選択基準
Spring SecurityのCSRFトークンは、通常はサーバー側セッションで管理されますが、Cookieにトークンを設定してフロントエンドと連携する方式も存在します。
Cookieベースの運用はSPA(Single Page Application)などで有効ですが、HTTPヘッダーでトークンを送信しないと検証エラーになるなど、実装が複雑になります。
今回のように、サーバーサイドでHTMLテンプレートを生成する構成では、セッション方式でのトークン管理のほうが初心者には扱いやすく、安全性も高いです。
(3)CSRFエラー発生時のユーザー対応
CSRFトークンが無効な場合、403 Forbiddenが返されてユーザーは困惑することがあります。
本番運用では、独自のエラーページを用意し、ユーザーに「再度ログインしてください」などの案内を表示すると親切です。
(4)セキュリティアップデートの継続的な適用
Spring Securityは継続的にアップデートされており、既知の脆弱性に対応するには定期的なライブラリ更新が不可欠です。
Gradleでバージョンを固定している場合でも、build.gradleでライブラリを見直す習慣を持ちましょう。
以上のようなポイントを意識することで、CSRF対策を含めたセキュリティ運用を本番環境で適切に行うことができます。