最新の安定バージョンについては、Spring Security 6.4.5 を使用してください! |
WebFlux 環境向けのクロスサイトリクエストフォージェリ(CSRF)
このセクションでは、WebFlux 環境に対する Spring Security のクロスサイトリクエストフォージェリ (CSRF) サポートについて説明します。
Spring Security CSRF 保護の使用
Spring Security の CSRF 保護を使用する手順の概要は次のとおりです。
適切な HTTP 動詞を使用する
CSRF 攻撃から保護するための最初のステップは、Web サイトが適切な HTTP 動詞を使用するようにすることです。これについては、安全なメソッドはべき等でなければなりませんで詳しく説明しています。
CSRF 保護を構成する
次のステップは、Spring Security の CSRF 保護をアプリケーション内で構成することです。Spring Security の CSRF 保護はデフォルトで有効になっていますが、構成をカスタマイズする必要がある場合があります。以下は、いくつかの一般的なカスタマイズです。
カスタム CsrfTokenRepository
デフォルトでは、Spring Security は、WebSessionServerCsrfTokenRepository
を使用して、期待される CSRF トークンを WebSession
に保存します。ユーザーがカスタム ServerCsrfTokenRepository
を構成したい場合があります。例: JavaScript ベースのアプリケーションをサポートするために、CsrfToken
を Cookie に保持することが望ましい場合があります。
デフォルトでは、CookieServerCsrfTokenRepository
は XSRF-TOKEN
という名前の Cookie に書き込み、X-XSRF-TOKEN
という名前のヘッダーまたは HTTP パラメーター _csrf
からそれを読み取ります。これらのデフォルトは AngularJS (英語) からのものです
以下を使用して、Java 構成で CookieServerCsrfTokenRepository
を構成できます。
Java
Kotlin
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))
return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
csrf {
csrfTokenRepository = CookieServerCsrfTokenRepository.withHttpOnlyFalse()
}
}
}
サンプルは |
CSRF 保護を無効にする
CSRF 保護はデフォルトで有効になっています。ただし、アプリケーションにとって意味がある場合は、CSRF 保護を無効にするのは簡単です。
以下の Java 構成は、CSRF 保護を無効にします。
Java
Kotlin
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.csrf(csrf -> csrf.disable()))
return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
csrf {
disable()
}
}
}
CSRF トークンを含める
シンクロナイザートークンパターンが CSRF 攻撃から保護されるようにするには、実際の CSRF トークンを HTTP リクエストに含める必要があります。これは、ブラウザーによって HTTP リクエストに自動的に含まれないリクエストの一部(つまり、フォームパラメーター、HTTP ヘッダーなど)に含まれている必要があります。
Spring Security の CsrfWebFilter (Javadoc) は、Mono <CsrfToken> (Javadoc) を org.springframework.security.web.server.csrf.CsrfToken
という名前の ServerWebExchange
属性として公開します。これは、任意のビューテクノロジが Mono<CsrfToken>
にアクセスして、期待されるトークンをフォームまたはメタタグとして公開できることを意味します。
ビューテクノロジーが Mono<CsrfToken>
をサブスクライブする簡単な方法を提供しない場合、一般的なパターンは Spring の @ControllerAdvice
を使用して CsrfToken
を直接公開することです。例: 次のコードは、Spring Security の CsrfRequestDataValueProcessor によって使用されるデフォルトの属性名(_csrf
)に CsrfToken
を配置して、CSRF トークンを非表示の入力として自動的に含めます。
@ModelAttribute
としての CsrfToken
Java
Kotlin
@ControllerAdvice
public class SecurityControllerAdvice {
@ModelAttribute
Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());
return csrfToken.doOnSuccess(token -> exchange.getAttributes()
.put(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME, token));
}
}
@ControllerAdvice
class SecurityControllerAdvice {
@ModelAttribute
fun csrfToken(exchange: ServerWebExchange): Mono<CsrfToken> {
val csrfToken: Mono<CsrfToken>? = exchange.getAttribute(CsrfToken::class.java.name)
return csrfToken!!.doOnSuccess { token ->
exchange.attributes[CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME] = token
}
}
}
幸いなことに、Thymeleaf は追加作業なしで機能する統合を提供します。
エンコードされたフォーム URL
HTML フォームを送信するには、CSRF トークンを非表示の入力としてフォームに含める必要があります。例: レンダリングされた HTML は次のようになります。
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
次に、CSRF トークンを非表示入力としてフォームに含めるさまざまな方法について説明します。
自動 CSRF トークンの包含
Spring Security の CSRF サポートは、CsrfRequestDataValueProcessor (Javadoc) を介して Spring の RequestDataValueProcessor (Javadoc) との統合を提供します。CsrfRequestDataValueProcessor
が機能するためには、Mono<CsrfToken>
をサブスクライブし、CsrfToken
を DEFAULT_CSRF_ATTR_NAME (Javadoc) に一致する属性として公開する必要があります。
幸い、Thymeleaf は、RequestDataValueProcessor
と統合することですべてのボイラープレートを処理するサポートを提供し (英語) 、安全でない HTTP メソッド(つまり post)を持つフォームに実際の CSRF トークンが自動的に含まれるようにします。
CsrfToken リクエスト属性
リクエストに実際の CSRF トークンを含めるための他のオプションが機能しない場合、Mono<CsrfToken>
が org.springframework.security.web.server.csrf.CsrfToken
という名前の ServerWebExchange
属性として公開されているという事実を利用できます。
以下の Thymeleaf サンプルは、_csrf
という名前の属性で CsrfToken
を公開することを前提としています。
<form th:action="@{/logout}"
method="post">
<input type="submit"
value="Log out" />
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}"/>
</form>
Ajax および JSON リクエスト
JSON を使用している場合、HTTP パラメーター内で CSRF トークンを送信することはできません。代わりに、HTTP ヘッダー内でトークンを送信できます。
次のセクションでは、JavaScript ベースのアプリケーションで CSRF トークンを HTTP リクエストヘッダーとして含めるさまざまな方法について説明します。
自動包含
Spring Security は、予想される CSRF トークンを Cookie に保存するように簡単に構成できます。期待される CSRF を Cookie に保存することにより、AngularJS (英語) などの JavaScript フレームワークは、実際の CSRF トークンを HTTP リクエストヘッダーに自動的に含めます。
メタタグ
Cookie で CSRF を公開する別のパターンは、meta
タグ内に CSRF トークンを含めることです。HTML は次のようになります。
<html>
<head>
<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
<!-- ... -->
</head>
<!-- ... -->
メタタグに CSRF トークンが含まれると、JavaScript コードはメタタグを読み取り、CSRF トークンをヘッダーとして含めます。jQuery を使用している場合は、次のように実行できます。
$(function () {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});
以下のサンプルでは、_csrf
という名前の属性で CsrfToken
を公開することを想定しています。Thymeleaf でこれを行う例を以下に示します。
<html>
<head>
<meta name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
<!-- ... -->
</head>
<!-- ... -->
CSRF の考慮事項
CSRF 攻撃に対する保護を実装する際に考慮すべきいくつかの特別な考慮事項があります。このセクションでは、WebFlux 環境に関連する考慮事項について説明します。より一般的な議論については、CSRF の考慮事項を参照してください。
ログイン
ログイン試行の偽造から保護するために、ログインリクエストに CSRF をリクエストすることが重要です。Spring Security の WebFlux サポートは、これをそのまま実行します。
ログアウト
ログアウトの試行を偽造しないように保護するために、ログアウトリクエストに CSRF をリクエストすることが重要です。デフォルトでは、Spring Security の LogoutWebFilter
は HTTP POST リクエストのみを処理します。これにより、ログアウトに CSRF トークンが必要になり、悪意のあるユーザーがユーザーを強制的にログアウトできないようになります。
最も簡単な方法は、フォームを使用してログアウトすることです。本当にリンクが必要な場合は、JavaScript を使用して、リンクに POST を実行させることができます(つまり、非表示のフォームで)。JavaScript が無効になっているブラウザーの場合、オプションで、POST を実行するログアウト確認ページにユーザーをリンクさせることができます。
本当にログアウトで HTTP GET を使用したい場合は使用できますが、これは一般的に推奨されないことを忘れないでください。例: 次の Java 構成は、任意の HTTP メソッドで /logout
がリクエストされた URL でログアウトを実行します。
Java
Kotlin
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.logout(logout -> logout.requiresLogout(new PathPatternParserServerWebExchangeMatcher("/logout")))
return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
logout {
requiresLogout = PathPatternParserServerWebExchangeMatcher("/logout")
}
}
}
CSRF およびセッションタイムアウト
デフォルトでは、Spring Security は CSRF トークンを WebSession
に保存します。これにより、セッションの有効期限が切れる状況が発生する可能性があります。つまり、検証対象の CSRF トークンが存在しないことがあります。
セッションタイムアウトの一般的な解決策についてはすでに説明しました。このセクションでは、WebFlux サポートに関連する CSRF タイムアウトの詳細について説明します。
予想される CSRF トークンのストレージを Cookie に変更するのは簡単です。詳細については、カスタム CsrfTokenRepository セクションを参照してください。
マルチパート (ファイルアップロード)
マルチパートリクエスト(ファイルのアップロード)を CSRF 攻撃から保護すると、鶏が先か卵が先か [Wikipedia] という問題がどのように発生するかについては、すでに説明しました。このセクションでは、CSRF トークンを WebFlux アプリケーション内の本文と URL に配置する方法について説明します。
Spring でのマルチパートフォームの使用に関する詳細情報は、Spring リファレンスのマルチパートデータセクションにあります。 |
CSRF トークンを本文に配置する
CSRF トークンを本体に配置することのトレードオフについてはすでに説明しました。
WebFlux アプリケーションでは、これは次の構成で構成できます。
Java
Kotlin
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.csrf(csrf -> csrf.tokenFromMultipartDataEnabled(true))
return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
csrf {
tokenFromMultipartDataEnabled = true
}
}
}
URL に CSRF トークンを含める
CSRF トークンを URL に配置することのトレードオフについてはすでに説明しました。 CsrfToken
は ServerHttpRequest
リクエスト属性として公開されているため、これを使用して、CSRF トークンを含む action
を作成できます。Thymeleaf の例を以下に示します。
<form method="post"
th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
enctype="multipart/form-data">
HiddenHttpMethodFilter
HTTP メソッドのオーバーライドについてはすでに説明しました。
Spring WebFlux アプリケーションでは、HiddenHttpMethodFilter (Javadoc) を使用して HTTP メソッドをオーバーライドします。