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

Exploit Protection の移行

次の手順は、CSRF の設定方法に関する変更に関連しています。

CsrfToken ロードの据え置き

Spring Security 5 では、デフォルトの動作は、すべてのリクエストで CsrfToken がロードされることです。これは、通常のセットアップでは、不要な場合でも、リクエストごとに HttpSession を読み取る必要があることを意味します。

セッションを読み取る必要のない場所の例には、静的アセット、静的 HTML ページ、同じドメイン / サーバーでホストされる単一ページアプリケーションなどの permitAll() とマークされたエンドポイントが含まれます。

Spring Security 6 では、デフォルトでは、CsrfToken のルックアップは必要になるまで延期されます。

CsrfToken は、アプリケーションの状態を変更する HTTP 動詞を使用してリクエストが行われるたびに必要になります。これは安全なメソッドは読み取り専用である必要があるで詳しく説明されています。さらに、CSRF トークンの非表示の <input> を含む <form> タグを持つ Web ページなど、トークンをレスポンスにレンダリングするすべてのリクエストで必要になります。

新しい Spring Security 6 デフォルトを選択するには、次の構成を使用できます。

遅延ロード CsrfToken
  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf");
	http
		// ...
		.csrf((csrf) -> csrf
			.csrfTokenRequestHandler(requestHandler)
		);
	return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
	val requestHandler = CsrfTokenRequestAttributeHandler()
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf")
	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"
	p:csrfRequestAttributeName="_csrf"/>

CsrfToken が延期されている場合 (Spring Security 6 のデフォルト)、一部のアプリケーションは、延期されていない CSRF トークンを使用して設計されているため、壊れる可能性があります。詳細については、以下のオプトアウトの手順を参照してください。

オプトアウトの手順

CsrfToken を延期するように構成すると問題が発生する場合は、最適なオプトアウト動作について次のシナリオを参照してください。

CookieCsrfTokenRepository でシングルページアプリケーションを使用しています

シングルページアプリ (SPA) を使用して Spring Security と CookieCsrfTokenRepository.withHttpOnlyFalse() によって保護されているバックエンドに接続している場合、サーバーへの最初のリクエストで CSRF トークンが Cookie としてアプリケーションに返されなくなっていることがあります。

この場合、クライアント側アプリケーションが期待する動作を復元するためのオプションがいくつかあります。1 つのオプションは、次のように、どのリクエストが最初に行われたかに関係なく、CsrfToken を積極的にレスポンスにレンダリングする Filter を追加することです。

Filter を追加して、レスポンスで Cookie を返す
  • Java

  • Kotlin

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
	CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf");
	http
		// ...
		.csrf((csrf) -> csrf
			.csrfTokenRepository(tokenRepository)
			.csrfTokenRequestHandler(requestHandler)
		)
		.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);

	return http.build();
}

private static final class CsrfCookieFilter extends OncePerRequestFilter {

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
		// Render the token value to a cookie by causing the deferred token to be loaded
		csrfToken.getToken();

		filterChain.doFilter(request, response);
	}

}
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
	val tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
	val requestHandler = CsrfTokenRequestAttributeHandler()
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf")
	http {
		csrf {
			csrfTokenRepository = tokenRepository
			csrfTokenRequestHandler = requestHandler
		}
		addFilterAfter<BasicAuthenticationFilter>(CsrfCookieFilter())
	}
	return http.build()
}

class CsrfCookieFilter : OncePerRequestFilter() {

	override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
		val csrfToken = request.getAttribute(CsrfToken::class.java.name) as CsrfToken
		// Render the token value to a cookie by causing the deferred token to be loaded
		csrfToken.token

		filterChain.doFilter(request, response)
	}

}

上記のオプションでは、単一ページのアプリケーションを変更する必要はありませんが、リクエストごとに CsrfToken が読み込まれます。Filter を追加してすべてのリクエストでトークンを積極的にロードしたくない場合は、追加のオプションを以下に示します。

HttpSessionCsrfTokenRepository でシングルページアプリケーションを使用しています

セッションを使用している場合、アプリケーションは遅延トークンの恩恵を受けます。オプトアウトする代わりに、次のように /csrf エンドポイントを持つ新しい @RestController を追加するという別のオプションがあります。

/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
    }

}

サーバーで認証する前に上記のエンドポイントが必要な場合は、.requestMatchers("/csrf").permitAll() の追加を検討してください。

/csrf エンドポイントは、後続のリクエストのためにアプリケーションをブートストラップするために、クライアント側アプリケーションによって消費される必要があります。

アプリケーションの起動時に /csrf エンドポイントを呼び出す手順は、クライアント側のフレームワークに固有であるため、このドキュメントの範囲外です。

これには単一ページのアプリケーションを変更する必要がありますが、利点は、CSRF トークンが 1 回だけ読み込まれ、トークンを引き続き延期できることです。このアプローチは、HttpSessionCsrfTokenRepository を使用し、すべてのリクエストで HttpSession を読み取らないようにすることで据え置きトークンの恩恵を受けるアプリケーションで特にうまく機能します。

延期されたトークンを完全にオプトアウトしたいだけの場合は、そのオプションが次にリストされています。

別の理由で遅延トークンをオプトアウトする必要がある

据え置きトークンが別の理由でアプリケーションを破損する場合は、次の構成を使用して明示的に 5.8 デフォルトを選択できます。

5.8 デフォルトで CsrfToken を明示的に設定する
  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName(null);
	http
		// ...
		.csrf((csrf) -> csrf
			.csrfTokenRequestHandler(requestHandler)
		);
	return http.build();
}
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
	val requestHandler = CsrfTokenRequestAttributeHandler()
	// 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>

csrfRequestAttributeName を null に設定することにより、最初に CsrfToken をロードして、使用する属性名を決定する必要があります。これにより、リクエストごとに CsrfToken がロードされます。

CSRF 違反からの保護

CsrfToken ロードの据え置きの手順がうまくいく場合は、次の構成を使用して、CsrfToken の BREACH 保護に対する Spring Security 6 のデフォルトサポートを選択することもできます。

CsrfToken 違反保護
  • Java

  • Kotlin

  • XML

@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf");
	http
		// ...
		.csrf((csrf) -> csrf
			.csrfTokenRequestHandler(requestHandler)
		);
	return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
	val requestHandler = XorCsrfTokenRequestAttributeHandler()
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf")
	http {
		csrf {
			csrfTokenRequestHandler = requestHandler
		}
	}
	return http.build()
}
<http>
	<!-- ... -->
	<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
	class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"
	p:csrfRequestAttributeName="_csrf"/>

オプトアウトの手順

CSRF BREACH 保護を構成すると問題が発生する場合は、次のシナリオを参照して、最適なオプトアウト動作を確認してください。

AngularJS または別の Javascript フレームワークを使用しています

AngularJS と HttpClientXsrfModule (英語) (または別のフレームワークの同様のモジュール) を CookieCsrfTokenRepository.withHttpOnlyFalse() と共に使用している場合、自動サポートが機能しなくなることがあります。

この場合、次のように、委譲のあるカスタム CsrfTokenRequestHandler を使用して、レスポンスの CSRF BREACH 保護を維持しながら、Cookie から生の CsrfToken を検証するように Spring Security を構成できます。

生のトークンを検証するために CsrfToken BREACH Protection を構成する
  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
	XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	delegate.setCsrfRequestAttributeName("_csrf");
	// Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
	// default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
	CsrfTokenRequestHandler requestHandler = delegate::handle;
	http
		// ...
		.csrf((csrf) -> csrf
			.csrfTokenRepository(tokenRepository)
			.csrfTokenRequestHandler(requestHandler)
		);

	return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
	val tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
	val delegate = XorCsrfTokenRequestAttributeHandler()
	// set the name of the attribute the CsrfToken will be populated on
	delegate.setCsrfRequestAttributeName("_csrf")
	// Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
	// default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
	val requestHandler = CsrfTokenRequestHandler(delegate::handle)
	http {
		csrf {
			csrfTokenRepository = tokenRepository
			csrfTokenRequestHandler = requestHandler
		}
	}
	return http.build()
}
<http>
	<!-- ... -->
	<csrf token-repository-ref="tokenRepository"
		request-handler-ref="requestHandler"/>
</http>
<b:bean id="tokenRepository"
	class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
	p:cookieHttpOnly="false"/>

これは、Cookie 値を使用するクライアント側アプリケーションで動作するように Spring Security を構成するための推奨される方法です。これは、アプリケーションが脆弱である可能性のある HTML またはその他のレスポンスを返した場合にレスポンスが CSRF トークンのランダム化された値を返すことを引き続き許可するためです。あなたの知らないうちに違反します。

違反保護は、GZIP 圧縮可能なレスポンス本文にトークンが含まれている場合に、トークンを保護するために機能します。これには通常、ヘッダーと Cookie は含まれません。

基になる (生の) CSRF トークンは変更されないため、サーバーから返されたトークン値はクライアント側アプリケーションで正常に使用できます。AngularJS (または同様の) アプリケーションがすべてのリクエストの前後に CSRF トークンをリフレッシュする必要はありません。

CSRF BREACH 保護を完全にオプトアウトしたいだけの場合は、そのオプションが次にリストされています。

別の理由で CSRF BREACH 保護をオプトアウトする必要があります

CSRF BREACH 保護が別の理由で機能しない場合は、遅延ロード CsrfToken セクションの構成を使用してオプトアウトできます。

WebSocket をサポートする CSRF 違反

CSRF 違反からの保護の手順が通常の HTTP リクエストで機能し、WebSocket セキュリティサポートを使用している場合は、ストンプヘッダーを使用した CsrfToken のブリーチ保護に対する Spring Security 6 のデフォルトサポートを選択することもできます。

WebSocket セキュリティ違反保護
  • Java

  • Kotlin

  • XML

@Bean
ChannelInterceptor csrfChannelInterceptor() {
	return new XorCsrfChannelInterceptor();
}
@Bean
open fun csrfChannelInterceptor(): ChannelInterceptor {
	return XorCsrfChannelInterceptor()
}
<b:bean id="csrfChannelInterceptor"
	class="org.springframework.security.messaging.web.csrf.XorCsrfChannelInterceptor"/>

WebSocket セキュリティの CSRF BREACH 保護を構成すると問題が発生する場合は、次の構成を使用して 5.8 のデフォルトを構成できます。

5.8 デフォルトで WebSocket セキュリティを構成する
  • Java

  • Kotlin

  • XML

@Bean
ChannelInterceptor csrfChannelInterceptor() {
	return new CsrfChannelInterceptor();
}
@Bean
open fun csrfChannelInterceptor(): ChannelInterceptor {
	return CsrfChannelInterceptor()
}
<b:bean id="csrfChannelInterceptor"
	class="org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor"/>