最新の安定バージョンについては、Spring Security 6.3.1 を使用してください!

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 を構成できます。

CSRF トークンを Cookie に保存する
  • 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()
        }
    }
}

サンプルは cookieHttpOnly=false を明示的に設定します。これは、JavaScript(つまり、AngularJS)が読み取れるようにするために必要です。JavaScript で Cookie を直接読み取る機能が必要ない場合は、cookieHttpOnly=false を省略して(代わりに new CookieServerCsrfTokenRepository() を使用して)セキュリティを向上させることをお勧めします。

CSRF 保護を無効にする

CSRF 保護はデフォルトで有効になっています。ただし、アプリケーションにとって意味がある場合は、CSRF 保護を無効にするのは簡単です。

以下の Java 構成は、CSRF 保護を無効にします。

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 は次のようになります。

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 を公開することを前提としています。

リクエスト属性を持つフォームの CSRF トークン
<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 は次のようになります。

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 を使用している場合、これは次の方法で実行できます。

AJAX 送信 CSRF トークン
$(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 でこれを行う例を以下に示します。

CSRF メタタグ JSP
<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 でログアウトを実行します。

HTTP GET でログアウトする
  • 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 アプリケーションでは、これは次の構成で構成できます。

multipart/form-data から CSRF トークンの取得を有効にします
  • 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 の例を以下に示します。

実行中の CSRF トークン
<form method="post"
	th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
	enctype="multipart/form-data">

HiddenHttpMethodFilter

HTTP メソッドのオーバーライドについてはすでに説明しました。

Spring WebFlux アプリケーションでは、HiddenHttpMethodFilter (Javadoc) を使用して HTTP メソッドをオーバーライドします。