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
パラメーターからその Cookie を読み取ります。これらのデフォルトは 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()
}
}
}
ServerCsrfTokenRequestHandler の設定
Spring Security の CsrfWebFilter
(Javadoc) は、ServerCsrfTokenRequestHandler
(Javadoc) の助けを借りて、org.springframework.security.web.server.csrf.CsrfToken
という名前の ServerWebExchange
属性として Mono<CsrfToken>
(Javadoc) を公開します。5.8 では、デフォルトの実装は ServerCsrfTokenRequestAttributeHandler
で、交換属性として Mono<CsrfToken>
を使用できるようにするだけです。
6.0 の時点で、デフォルトの実装は XorServerCsrfTokenRequestAttributeHandler
であり、BREACH に対する保護を提供します ( gh-4001 [GitHub] (英語) を参照)。
CsrfToken
の BREACH 保護を無効にして 5.8 のデフォルトに戻す場合は、次の Java 構成を使用して ServerCsrfTokenRequestAttributeHandler
を構成できます。
Java
Kotlin
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.csrf(csrf -> csrf
.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler())
)
return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
csrf {
csrfTokenRequestHandler = ServerCsrfTokenRequestAttributeHandler()
}
}
}
CSRF トークンを含める
シンクロナイザートークンパターンが CSRF 攻撃から保護するには、実際の CSRF トークンを HTTP リクエストに含める必要があります。ブラウザーによって HTTP リクエストに自動的に含まれないリクエストの一部(フォームパラメーター、HTTP ヘッダー、その他のオプション)に含める必要があります。
Mono<CsrfToken>
が ServerWebExchange
属性として公開されている見た。これは、任意のビューテクノロジーが Mono<CsrfToken>
にアクセスして、予期されるトークンをフォームまたはメタタグとして公開できることを意味します。
ビューテクノロジーが Mono<CsrfToken>
をサブスクライブする簡単な方法を提供しない場合、一般的なパターンは Spring の @ControllerAdvice
を使用して CsrfToken
を直接公開することです。次の例では、CsrfToken
を Spring Security の CsrfRequestDataValueProcessor で使用されるデフォルトの属性名(_csrf
)に配置して、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 ベースのアプリケーションで HTTP リクエストヘッダーとして CSRF トークンを含めるさまざまな方法について説明します。
自動包含
予想される CSRF トークンを Cookie に保存するように Spring Security を構成できます。予期される 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 アプリケーションでは、HTTP メソッドのオーバーライドは HiddenHttpMethodFilter
(Javadoc) を使用して行われます。