クロスサイトリクエストフォージェリ (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 (英語) からのものです。
このトピックに関する最新情報については、クロスサイトリクエストフォージェリ (XSRF) からの保護 (英語) ガイドおよび HttpClientXsrfModule (英語) を参照してください。 |
次の構成を使用して 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
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) (1)
.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) (2)
);
return http.build();
}
}
final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler {
private final CsrfTokenRequestHandler plain = new CsrfTokenRequestAttributeHandler();
private final CsrfTokenRequestHandler xor = new XorCsrfTokenRequestAttributeHandler();
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
/*
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
* the CsrfToken when it is rendered in the response body.
*/
this.xor.handle(request, response, csrfToken);
/*
* Render the token value to a cookie by causing the deferred token to be loaded.
*/
csrfToken.get();
}
@Override
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
String headerValue = request.getHeader(csrfToken.getHeaderName());
/*
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
* to resolve the CsrfToken. This applies when a single-page application includes
* the header value automatically, which was obtained via a cookie containing the
* raw CsrfToken.
*
* In all other cases (e.g. if the request contains a request parameter), use
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
* when a server-side rendered form includes the _csrf request parameter as a
* hidden input.
*/
return (StringUtils.hasText(headerValue) ? this.plain : this.xor).resolveCsrfTokenValue(request, csrfToken);
}
}
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
csrf {
csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() (1)
csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() (2)
}
}
return http.build()
}
}
class SpaCsrfTokenRequestHandler : CsrfTokenRequestHandler {
private val plain: CsrfTokenRequestHandler = CsrfTokenRequestAttributeHandler()
private val xor: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
override fun handle(request: HttpServletRequest, response: HttpServletResponse, csrfToken: Supplier<CsrfToken>) {
/*
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
* the CsrfToken when it is rendered in the response body.
*/
xor.handle(request, response, csrfToken)
/*
* Render the token value to a cookie by causing the deferred token to be loaded.
*/
csrfToken.get()
}
override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String? {
val headerValue = request.getHeader(csrfToken.headerName)
/*
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
* to resolve the CsrfToken. This applies when a single-page application includes
* the header value automatically, which was obtained via a cookie containing the
* raw CsrfToken.
*/
return if (StringUtils.hasText(headerValue)) {
plain
} else {
/*
* In all other cases (e.g. if the request contains a request parameter), use
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
* when a server-side rendered form includes the _csrf request parameter as a
* hidden input.
*/
xor
}.resolveCsrfTokenValue(request, csrfToken)
}
}
<http>
<!-- ... -->
<csrf
token-repository-ref="tokenRepository" (1)
request-handler-ref="requestHandler"/> (2)
</http>
<b:bean id="tokenRepository"
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
p:cookieHttpOnly="false"/>
<b:bean id="requestHandler"
class="example.SpaCsrfTokenRequestHandler"/>
1 | JavaScript アプリケーションが Cookie を読み取れるように、HttpOnly を false に設定して CookieCsrfTokenRepository を構成します。 |
2 | CSRF トークンが HTTP リクエストヘッダー (X-XSRF-TOKEN ) であるかリクエストパラメーター (_csrf ) であるかに基づいて CSRF トークンを解決するカスタム CsrfTokenRequestHandler を構成します。この実装により、遅延 CsrfToken がリクエストごとに読み込まれ、必要に応じて新しい Cookie が返されます。 |
複数ページのアプリケーション
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.web.util.matcher.AntPathRequestMatcher">
<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(new AntPathRequestMatcher("/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 = AntPathRequestMatcher("/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 ファイアウォールなどのエクスプロイト保護についてさらに学習するか、アプリケーションのテスト方法の学習に進むことを検討してください。