クロスサイトリクエストフォージェリ (CSRF)
エンドユーザーがログインできるアプリケーションでは、クロスサイトリクエストフォージェリ (CSRF) から保護する方法を検討することが重要です。
Spring Security は、デフォルトで POST リクエストなどの安全でない HTTP メソッドに対する CSRF 攻撃から保護するため、追加のコードは必要ありません。以下を使用して、デフォルト構成を明示的に指定できます。
Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf(Customizer.withDefaults());
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
csrf { }
}
return http.build()
}
}
<http>
<!-- ... -->
<csrf/>
</http>
アプリケーションの CSRF 保護の詳細については、次の使用例を検討してください。
CsrfTokenをセッションではなく Cookie に保存したいThymeleaf、JSP、別のビューテクノロジとバックエンドを統合するためのガイダンスが必要です
Angular または別の JavaScript フレームワークとバックエンドを統合するためのガイダンスが必要です
モバイルアプリケーションまたは別のクライアントとバックエンドを統合するためのガイダンスが必要です
エラーの処理に関するガイダンスが必要です
CSRF 保護をテストしたい
CSRF 保護を無効にするためのガイダンスが必要です
CSRF 保護のコンポーネントを理解する
CSRF 保護は、CsrfFilter (Javadoc) 内で構成されるいくつかのコンポーネントによって提供されます。

CsrfFilter のコンポーネント CSRF 保護は 2 つの部分に分かれています。
CsrfTokenRequestHandlerに委譲することで、アプリケーションでCsrfToken(Javadoc) を使用できるようにします。リクエストに CSRF 保護が必要かどうかを判断し、トークンをロードして検証し、
AccessDeniedExceptionを処理します。

CsrfFilter 処理
まず、DeferredCsrfToken(Javadoc) がロードされます。これは、永続化されたCsrfTokenを後でロードできるように、CsrfTokenRepositoryへの参照を保持します (
)。
次に、Supplier<CsrfToken>(DeferredCsrfTokenから作成) がCsrfTokenRequestHandlerに与えられます。CsrfTokenRequestHandlerは、アプリケーションの残りの部分でCsrfTokenを使用できるようにするためのリクエスト属性を設定します。
次に、メインの CSRF 保護処理が開始され、現在のリクエストに CSRF 保護が必要かどうかがチェックされます。必要がなければ、フィルターチェーンを継続して処理を終了します。
CSRF 保護が必要な場合は、永続化された CsrfTokenが最終的にDeferredCsrfTokenからロードされます。
続いて、クライアントによって提供された実際の CSRF トークン (存在する場合) は、CsrfTokenRequestHandlerを使用して解決されます。
実際の CSRF トークンは、永続化された CsrfTokenと比較されます。有効な場合、フィルターチェーンは継続され、処理は終了します。
実際の CSRF トークンが無効 (または欠落) の場合、AccessDeniedExceptionがAccessDeniedHandlerに渡され、処理は終了します。
Spring Security 6 への移行
Spring Security 5 から 6 に移行する場合、アプリケーションに影響を与える可能性のある変更がいくつかあります。以下は、Spring Security 6 で変更された CSRF 保護の側面の概要です。
CsrfTokenのロードがデフォルトで延期されるようになり、リクエストごとにセッションをロードする必要がなくなり、パフォーマンスが向上しました。CsrfTokenには、CSRF トークンを BREACH [Wikipedia] (英語) 攻撃から保護するために、デフォルトですべてのリクエストにランダム性が含まれるようになりました。
Spring Security 6 の変更にはシングルページアプリケーション用の追加構成が必要なため、シングルページアプリケーションセクションが特に役立つと思われます。 |
CsrfToken の永続化
CsrfToken は CsrfTokenRepository を使用して永続化されます。
デフォルトでは、HttpSessionCsrfTokenRepository はセッション内のトークンの保存に使用されます。Spring Security は、トークンを Cookie に保存するための CookieCsrfTokenRepository も提供します。独自の実装を指定して、好きな場所にトークンを保存することもできます。
HttpSessionCsrfTokenRepository を使用する
デフォルトでは、Spring Security は HttpSessionCsrfTokenRepository (Javadoc) を使用して予期される CSRF トークンを HttpSession に保存するため、追加のコードは必要ありません。
HttpSessionCsrfTokenRepository はセッション (メモリ内、キャッシュ、データベース) からトークンを読み取ります。セッション属性に直接アクセスする必要がある場合は、まず HttpSessionCsrfTokenRepository#setSessionAttributeName を使用してセッション属性名を構成してください。
次の構成を使用して、デフォルト構成を明示的に指定できます。
HttpSessionCsrfTokenRepository を構成する Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRepository(new HttpSessionCsrfTokenRepository())
);
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
csrf {
csrfTokenRepository = HttpSessionCsrfTokenRepository()
}
}
return http.build()
}
}
<http>
<!-- ... -->
<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository"/>
CookieCsrfTokenRepository を使用する
CsrfToken を Cookie に保存して、CookieCsrfTokenRepository (Javadoc) を使用する JavaScript ベースのアプリケーションをサポートできます。
CookieCsrfTokenRepository は、デフォルトで XSRF-TOKEN という名前の Cookie に書き込み、X-XSRF-TOKEN という名前の HTTP リクエストヘッダーまたはリクエストパラメーター _csrf からそれを読み取ります。これらのデフォルトは、Angular およびその前身である AngularJS (英語) からのものです。
このトピックの最新情報については、HttpClient XSRF/CSRF セキュリティ (英語) および withXsrfConfiguration (英語) を参照してください。 |
次の構成を使用して CookieCsrfTokenRepository を構成できます。
この例では、 |
CsrfTokenRepository のカスタマイズ
カスタム CsrfTokenRepository (Javadoc) を実装したい場合があります。
CsrfTokenRepository インターフェースを実装したら、次の構成でそれを使用するように Spring Security を構成できます。
CsrfTokenRepository の構成 Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRepository(new CustomCsrfTokenRepository())
);
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
csrf {
csrfTokenRepository = CustomCsrfTokenRepository()
}
}
return http.build()
}
}
<http>
<!-- ... -->
<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
class="example.CustomCsrfTokenRepository"/>
CsrfToken の取り扱いについて
CsrfToken は、CsrfTokenRequestHandler を使用するアプリケーションで使用できるようになります。このコンポーネントは、HTTP ヘッダーまたはリクエストパラメーターから CsrfToken を解決するロールも果たします。
デフォルトでは、XorCsrfTokenRequestAttributeHandler は CsrfToken の BREACH [Wikipedia] (英語) 保護を提供するために使用されます。Spring Security は、BREACH 保護をオプトアウトするための CsrfTokenRequestAttributeHandler も提供します。独自の実装を指定して、トークンの処理と解決の戦略をカスタマイズすることもできます。
XorCsrfTokenRequestAttributeHandler を使用する (BREACH)
XorCsrfTokenRequestAttributeHandler は、CsrfToken を _csrf と呼ばれる HttpServletRequest 属性として使用できるようにし、さらに BREACH [Wikipedia] (英語) の保護を提供します。
|
この実装では、リクエストのトークン値もリクエストヘッダー (デフォルトでは X-CSRF-TOKEN または X-XSRF-TOKEN のいずれか) またはリクエストパラメーター (デフォルトでは _csrf ) として解決されます。
BREACH 保護は、ランダム性を CSRF トークン値にエンコードすることで提供され、返される |
Spring Security はデフォルトで CSRF トークンを BREACH 攻撃から保護するため、追加のコードは必要ありません。次の構成を使用して、デフォルト構成を明示的に指定できます。
Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler())
);
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
csrf {
csrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
}
}
return http.build()
}
}
<http>
<!-- ... -->
<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"/>
CsrfTokenRequestAttributeHandler を使用する
CsrfTokenRequestAttributeHandler は、CsrfToken を _csrf と呼ばれる HttpServletRequest 属性として使用できるようにします。
|
この実装では、リクエストのトークン値もリクエストヘッダー (デフォルトでは X-CSRF-TOKEN または X-XSRF-TOKEN のいずれか) またはリクエストパラメーター (デフォルトでは _csrf ) として解決されます。
CsrfTokenRequestAttributeHandler の主な用途は、CsrfToken の BREACH 保護をオプトアウトすることです。これは、次の構成を使用して構成できます。
Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
);
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
csrf {
csrfTokenRequestHandler = CsrfTokenRequestAttributeHandler()
}
}
return http.build()
}
}
<http>
<!-- ... -->
<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"/>
CsrfTokenRequestHandler のカスタマイズ
CsrfTokenRequestHandler インターフェースを実装して、トークンの処理と解決の戦略をカスタマイズできます。
|
CsrfTokenRequestHandler インターフェースを実装したら、次の構成でそれを使用するように Spring Security を構成できます。
CsrfTokenRequestHandler の構成 Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRequestHandler(new CustomCsrfTokenRequestHandler())
);
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
csrf {
csrfTokenRequestHandler = CustomCsrfTokenRequestHandler()
}
}
return http.build()
}
}
<http>
<!-- ... -->
<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
class="example.CustomCsrfTokenRequestHandler"/>
CsrfToken の遅延ロード
デフォルトでは、Spring Security は必要になるまで CsrfToken のロードを延期します。
|
Spring Security はデフォルトで CsrfToken を HttpSession にも格納するため、遅延 CSRF トークンにより、リクエストごとにセッションをロードする必要がなくなり、パフォーマンスが向上します。
遅延トークンをオプトアウトして、すべてのリクエストで CsrfToken をロードするようにしたい場合は、次の構成で行うことができます。
Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
// set the name of the attribute the CsrfToken will be populated on
requestHandler.setCsrfRequestAttributeName(null);
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRequestHandler(requestHandler)
);
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
val requestHandler = XorCsrfTokenRequestAttributeHandler()
// set the name of the attribute the CsrfToken will be populated on
requestHandler.setCsrfRequestAttributeName(null)
http {
// ...
csrf {
csrfTokenRequestHandler = requestHandler
}
}
return http.build()
}
}
<http>
<!-- ... -->
<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler">
<b:property name="csrfRequestAttributeName">
<b:null/>
</b:property>
</b:bean>
|
CSRF 保護との統合
シンクロナイザートークンパターンが CSRF 攻撃から保護するには、実際の CSRF トークンを HTTP リクエストに含める必要があります。これは、ブラウザーによって HTTP リクエストに自動的に含まれないリクエストの一部(フォームパラメーター、HTTP ヘッダー、その他の部分)に含まれている必要があります。
次のセクションでは、フロントエンドまたはクライアントアプリケーションを CSRF で保護されたバックエンドアプリケーションと統合できるさまざまな方法について説明します。
HTML フォーム
HTML フォームを送信するには、CSRF トークンを非表示の入力としてフォームに含める必要があります。例: レンダリングされた HTML は次のようになります:
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>次のビューテクノロジーでは、POST などの安全でない HTTP メソッドを含むフォームに実際の CSRF トークンが自動的に含まれます。
RequestDataValueProcessor(Javadoc) と統合されるその他のビューテクノロジー (CsrfRequestDataValueProcessor(Javadoc) 経由)csrfInput タグを介してトークンを自分で含めることもできます
これらのオプションが利用できない場合は、CsrfToken が _csrf という名前の HttpServletRequest 属性として公開されるという事実を利用できます。次の例では、JSP を使用してこれを実行します。
<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}"
method="post">
<input type="submit"
value="Log out" />
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
</form>JavaScript アプリケーション
JavaScript アプリケーションは通常、HTML ではなく JSON を使用します。JSON を使用する場合は、リクエストパラメーターの代わりに HTTP リクエストヘッダー内で CSRF トークンを送信できます。
CSRF トークンを取得するには、予期される CSRF トークンを Cookie に保存するように Spring Security を構成します。予期されるトークンを Cookie に保存することにより、Angular (英語) などの JavaScript フレームワークは、実際の CSRF トークンを HTTP リクエストヘッダーとして自動的に含めることができます。
シングルページアプリケーション (SPA) を Spring Security の CSRF 保護と統合する場合は、BREACH 保護と遅延トークンについて特別な考慮事項があります。完全な構成例は次のセクションで説明します。 |
次のセクションでは、さまざまな型の JavaScript アプリケーションについて説明します。
シングルページアプリケーション
シングルページアプリケーション (SPA) を Spring Security の CSRF 保護と統合するには、特別な考慮事項があります。
Spring Security はデフォルトで CsrfToken の BREACH 保護を提供することを思い出してください。予期される CSRF トークンを Cookie に保存する場合、JavaScript アプリケーションはプレーントークン値にのみアクセスでき、エンコードされた値にはアクセスできません。実際のトークン値を解決するためにカスタマイズされたリクエストハンドラーを提供する必要があります。
さらに、CSRF トークンを保存する Cookie は、認証成功およびログアウト成功時にクリアされます。Spring Security はデフォルトで新しい CSRF トークンのロードを延期し、新しい Cookie を返すには追加の作業が必要です。
|
シングルページアプリケーションを Spring Security と簡単に統合するには、次の構成を使用できます。
Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf.spa());
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
csrf {
spa()
}
}
return http.build()
}
}
<http>
<!-- ... -->
<csrf>
<spa />
</csrf>
</http>
複数ページのアプリケーション
JavaScript が各ページに読み込まれるマルチページアプリケーションの場合、Cookie で CSRF トークンを公開する代わりに、CSRF トークンを meta タグ内に含めることができます。HTML は次のようになります。
<html>
<head>
<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
<!-- ... -->
</head>
<!-- ... -->
</html> リクエストに CSRF トークンを含めるには、CsrfToken が _csrf という名前の HttpServletRequest 属性として公開されるという事実を利用できます。次の例では、JSP を使用してこれを実行します。
<html>
<head>
<meta name="_csrf" content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}"/>
<!-- ... -->
</head>
<!-- ... -->
</html>メタタグに 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);
});
});その他の JavaScript アプリケーション
JavaScript アプリケーションの別のオプションは、HTTP レスポンスヘッダーに CSRF トークンを含めることです。
これを実現する 1 つの方法は、@ControllerAdvice を CsrfTokenArgumentResolver とともに使用することです。以下は、アプリケーション内のすべてのコントローラーエンドポイントに適用される @ControllerAdvice の例です。
Java
Kotlin
@ControllerAdvice
public class CsrfControllerAdvice {
@ModelAttribute
public void getCsrfToken(HttpServletResponse response, CsrfToken csrfToken) {
response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken());
}
}
@ControllerAdvice
class CsrfControllerAdvice {
@ModelAttribute
fun getCsrfToken(response: HttpServletResponse, csrfToken: CsrfToken) {
response.setHeader(csrfToken.headerName, csrfToken.token)
}
}
この |
コントローラーエンドポイントとコントローラーアドバイスは、Spring Security フィルターチェーンの後に呼び出されることを覚えておくことが重要です。これは、リクエストがフィルターチェーンを通過してアプリケーションに渡される場合にのみ、この |
CSRF トークンは、コントローラーのアドバイスが適用されるカスタムエンドポイントのレスポンスヘッダー (デフォルトでは X-CSRF-TOKEN または X-XSRF-TOKEN) で使用できるようになります。バックエンドへのリクエストはすべて、レスポンスからトークンを取得するために使用でき、後続のリクエストでは同じ名前のリクエストヘッダーにトークンを含めることができます。
モバイルアプリケーション
JavaScript アプリケーションと同様、モバイルアプリケーションは通常、HTML ではなく JSON を使用します。ブラウザートラフィックを処理しないバックエンドアプリケーションは、CSRF を無効にすることを選択する場合があります。その場合、追加の作業は必要ありません。
ただし、ブラウザートラフィックも処理するため、依然として CSRF 保護が必要なバックエンドアプリケーションは、Cookie ではなくセッションに CsrfToken を保存し続ける可能性があります。
この場合、バックエンドと統合するための一般的なパターンは、/csrf エンドポイントを公開して、フロントエンド (モバイルまたはブラウザークライアント) がオンデマンドで CSRF トークンをリクエストできるようにすることです。このパターンを使用する利点は、CSRF トークンを引き続き延期でき、リクエストで CSRF 保護が必要な場合にのみセッションからロードする必要があることです。カスタムエンドポイントの使用は、クライアントアプリケーションが明示的なリクエストを発行することで、(必要に応じて) 新しいトークンをオンデマンドで生成することをリクエストできることも意味します。
このパターンは、モバイルアプリケーションだけでなく、CSRF 保護を必要とするあらゆる種類のアプリケーションに使用できます。このようなケースでは通常、このアプローチは必要ありませんが、CSRF で保護されたバックエンドと統合するためのもう 1 つのオプションです。 |
以下は、CsrfTokenArgumentResolver を使用する /csrf エンドポイントの例です。
/csrf エンドポイント Java
Kotlin
@RestController
public class CsrfController {
@GetMapping("/csrf")
public CsrfToken csrf(CsrfToken csrfToken) {
return csrfToken;
}
}
@RestController
class CsrfController {
@GetMapping("/csrf")
fun csrf(csrfToken: CsrfToken): CsrfToken {
return csrfToken
}
}
サーバーで認証する前に上記のエンドポイントが必要な場合は、 |
このエンドポイントは、アプリケーションの起動時または初期化時 (ロード時など)、および認証成功およびログアウト成功後にも呼び出されて、CSRF トークンを取得する必要があります。
|
CSRF トークンを取得したら、それを HTTP リクエストヘッダー (デフォルトでは X-CSRF-TOKEN または X-XSRF-TOKEN のいずれか) として自分で含める必要があります。
AccessDeniedException の処理
InvalidCsrfTokenException などの AccessDeniedException を処理するには、これらの例外を任意の方法で処理するように Spring Security を構成できます。例: 次の構成を使用して、カスタムのアクセス拒否ページを構成できます。
AccessDeniedHandler を構成する Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.exceptionHandling((exceptionHandling) -> exceptionHandling
.accessDeniedPage("/access-denied")
);
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
exceptionHandling {
accessDeniedPage = "/access-denied"
}
}
return http.build()
}
}
<http>
<!-- ... -->
<access-denied-handler error-page="/access-denied"/>
</http>
CSRF テスト
次のように、Spring Security のテストサポートと CsrfRequestPostProcessor を使用して CSRF 保護をテストできます。
Java
Kotlin
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SecurityConfig.class)
@WebAppConfiguration
public class CsrfTests {
private MockMvc mockMvc;
@BeforeEach
public void setUp(WebApplicationContext applicationContext) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext)
.apply(springSecurity())
.build();
}
@Test
public void loginWhenValidCsrfTokenThenSuccess() throws Exception {
this.mockMvc.perform(post("/login").with(csrf())
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().is3xxRedirection())
.andExpect(header().string(HttpHeaders.LOCATION, "/"));
}
@Test
public void loginWhenInvalidCsrfTokenThenForbidden() throws Exception {
this.mockMvc.perform(post("/login").with(csrf().useInvalidToken())
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden());
}
@Test
public void loginWhenMissingCsrfTokenThenForbidden() throws Exception {
this.mockMvc.perform(post("/login")
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden());
}
@Test
@WithMockUser
public void logoutWhenValidCsrfTokenThenSuccess() throws Exception {
this.mockMvc.perform(post("/logout").with(csrf())
.accept(MediaType.TEXT_HTML))
.andExpect(status().is3xxRedirection())
.andExpect(header().string(HttpHeaders.LOCATION, "/login?logout"));
}
}
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [SecurityConfig::class])
@WebAppConfiguration
class CsrfTests {
private lateinit var mockMvc: MockMvc
@BeforeEach
fun setUp(applicationContext: WebApplicationContext) {
mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext)
.apply<DefaultMockMvcBuilder>(springSecurity())
.build()
}
@Test
fun loginWhenValidCsrfTokenThenSuccess() {
mockMvc.perform(post("/login").with(csrf())
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().is3xxRedirection)
.andExpect(header().string(HttpHeaders.LOCATION, "/"))
}
@Test
fun loginWhenInvalidCsrfTokenThenForbidden() {
mockMvc.perform(post("/login").with(csrf().useInvalidToken())
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden)
}
@Test
fun loginWhenMissingCsrfTokenThenForbidden() {
mockMvc.perform(post("/login")
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden)
}
@Test
@WithMockUser
@Throws(Exception::class)
fun logoutWhenValidCsrfTokenThenSuccess() {
mockMvc.perform(post("/logout").with(csrf())
.accept(MediaType.TEXT_HTML))
.andExpect(status().is3xxRedirection)
.andExpect(header().string(HttpHeaders.LOCATION, "/login?logout"))
}
}
CSRF 保護を無効にする
デフォルトでは、CSRF 保護が有効になっており、バックエンドとの統合とアプリケーションのテストに影響します。CSRF 保護を無効にする前に、それがアプリケーションにとって意味があるかどうかを検討してください。
次の例のように、特定のエンドポイントだけが CSRF 保護を必要としないかどうかを検討し、無視ルールを設定することもできます。
Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.ignoringRequestMatchers("/api/*")
);
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
csrf {
ignoringRequestMatchers("/api/*")
}
}
return http.build()
}
}
<http>
<!-- ... -->
<csrf request-matcher-ref="csrfMatcher"/>
</http>
<b:bean id="csrfMatcher"
class="org.springframework.security.web.util.matcher.AndRequestMatcher">
<b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/>
<b:constructor-arg>
<b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
<b:bean class="org.springframework.security.config.http.PathPatternRequestMatcherFactoryBean">
<b:constructor-arg value="/api/*"/>
</b:bean>
</b:bean>
</b:constructor-arg>
</b:bean>
CSRF 保護を無効にする必要がある場合は、次の構成を使用して無効にすることができます。
Java
Kotlin
XML
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf.disable());
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
csrf {
disable()
}
}
return http.build()
}
}
<http>
<!-- ... -->
<csrf disabled="true"/>
</http>
CSRF の考慮事項
CSRF 攻撃に対する保護を実装する場合は、特別な考慮事項がいくつかあります。このセクションでは、サーブレット環境に関連する考慮事項について説明します。より一般的な説明については、CSRF の考慮事項を参照してください。
ログイン
ログイン試行の偽造から保護するために、ログインリクエストに CSRF をリクエストすることが重要です。Spring Security のサーブレットサポートは、これをそのまま実行します。
ログアウト
偽造されたログアウト試行から保護するには、ログアウトリクエストに CSRF をリクエストすることが重要です。CSRF 保護が有効になっている場合 (デフォルト)、Spring Security の LogoutFilter は HTTP POST リクエストのみを処理します。これにより、ログアウトには CSRF トークンが必要となり、悪意のあるユーザーがユーザーを強制的にログアウトすることができなくなります。
最も簡単な方法は、フォームを使用してユーザーをログアウトすることです。本当にリンクが必要な場合は、JavaScript を使用してリンクに POST を実行させることができます (おそらく非表示のフォーム上で)。JavaScript が無効になっているブラウザーの場合、オプションで、POST を実行するログアウト確認ページにユーザーを誘導するリンクを設定できます。
本当にログアウトしながら HTTP GET を使用したい場合は、そうすることができます。ただし、これは一般的に推奨されないことに注意してください。例: 次の例は、HTTP メソッドで /logout URL がリクエストされたときにログアウトします。
Java
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.logout((logout) -> logout
.logoutRequestMatcher(PathPatternRequestMatcher.withDefaults().matcher("/logout"))
);
return http.build();
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
logout {
logoutRequestMatcher = PathPatternRequestMatcher.withDefaults().matcher("/logout")
}
}
return http.build()
}
}
詳細については、ログアウトの章を参照してください。
CSRF およびセッションタイムアウト
デフォルトでは、Spring Security は HttpSessionCsrfTokenRepository を使用して CSRF トークンを HttpSession に保存します。これにより、セッションが期限切れになり、検証する CSRF トークンが残らないという状況が発生する可能性があります。
セッションタイムアウトの一般的な解決策についてはすでに説明しました。このセクションでは、サーブレットのサポートに関連する CSRF タイムアウトの詳細について説明します。
CSRF トークンのストレージを Cookie に変更することができます。詳細については、CookieCsrfTokenRepository を使用するのセクションを参照してください。
トークンの有効期限が切れた場合は、カスタム AccessDeniedHandler を指定してトークンの処理方法をカスタマイズすることができます。カスタム AccessDeniedHandler は、InvalidCsrfTokenException を任意の方法で処理できます。
マルチパート (ファイルアップロード)
マルチパートリクエスト (ファイルアップロード) を CSRF 攻撃から保護することが、鶏が先か卵が先か [Wikipedia] の課題を引き起こす仕組みについてはすでに説明しました。JavaScript が利用可能な場合は、課題を回避するために HTTP リクエストヘッダーに CSRF トークンを含めること をお勧めします。
Spring でマルチパートフォームを使用する方法の詳細については、Spring リファレンスのマルチパートリゾルバーセクションと |
CSRF トークンを本文に配置する
CSRF トークンを本体に配置することのトレードオフについてはすでに説明しました。このセクションでは、本体から CSRF を読み取るように Spring Security を構成する方法について説明します。
本体から CSRF トークンを読み取るには、Spring Security フィルターの前に MultipartFilter を指定します。Spring Security フィルターの前に MultipartFilter を指定することは、MultipartFilter を呼び出すための認可がないことを意味します。つまり、誰でもサーバーに一時ファイルを置くことができます。ただし、アプリケーションによって処理されるファイルを送信できるのは、認可されたユーザーのみです。一般に、一時ファイルのアップロードによるほとんどのサーバーへの影響はごくわずかであるため、これが推奨されるアプローチです。
MultipartFilter を構成する Java
Kotlin
XML
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
@Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
}
class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer() {
override fun beforeSpringSecurityFilterChain(servletContext: ServletContext?) {
insertFilters(servletContext, MultipartFilter())
}
}
<filter>
<filter-name>MultipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>MultipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
XML 構成で |
URL に CSRF トークンを含める
権限のないユーザーに一時ファイルのアップロードを認可しない場合は、MultipartFilter を Spring Security フィルターの後に配置し、フォームのアクション属性にクエリパラメーターとして CSRF を含めることもできます。CsrfToken は _csrf という名前の HttpServletRequest 属性として公開されているため、それを使用して CSRF トークンを含む action を作成できます。次の例では、JSP を使用してこれを実行します。
<form method="post"
action="./upload?${_csrf.parameterName}=${_csrf.token}"
enctype="multipart/form-data">HiddenHttpMethodFilter
CSRF トークンを本体に配置することのトレードオフについてはすでに説明しました。
Spring のサーブレットサポートでは、HTTP メソッドのオーバーライドは HiddenHttpMethodFilter (Javadoc) を使用して行われます。詳細については、リファレンスドキュメントの HTTP メソッド変換セクションを参照してください。
参考文献
CSRF 保護について確認したため、セキュアヘッダーや HTTP ファイアウォールなどのエクスプロイト保護についてさらに学習するか、アプリケーションのテスト方法の学習に進むことを検討してください。