このバージョンはまだ開発中であり、まだ安定しているとは見なされていません。最新の安定バージョンについては、Spring Security 6.5.5 を使用してください!

WebFlux セキュリティ

Spring Security の WebFlux サポートは WebFilter に依存しており、Spring WebFlux および Spring WebFlux.Fn でも同じように機能します。いくつかのサンプルアプリケーションがコードを示しています。

最小限の WebFlux セキュリティ構成

次のリストは、最小限の WebFlux セキュリティ構成を示しています。

最小限の WebFlux セキュリティ構成
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {

	@Bean
	public MapReactiveUserDetailsService userDetailsService() {
		UserDetails user = User.withDefaultPasswordEncoder()
			.username("user")
			.password("user")
			.roles("USER")
			.build();
		return new MapReactiveUserDetailsService(user);
	}
}
@Configuration
@EnableWebFluxSecurity
class HelloWebfluxSecurityConfig {

    @Bean
    fun userDetailsService(): ReactiveUserDetailsService {
        val userDetails = User.withDefaultPasswordEncoder()
                .username("user")
                .password("user")
                .roles("USER")
                .build()
        return MapReactiveUserDetailsService(userDetails)
    }
}

この構成は、フォームと HTTP の基本認証を提供し、任意のページにアクセスするために認証されたユーザーを要求する認可を設定し、デフォルトのログインページとデフォルトのログアウトページを設定し、セキュリティ関連の HTTP ヘッダーを設定し、CSRF 保護を追加します。

明示的な WebFlux セキュリティ構成

次のページは、最小限の WebFlux セキュリティ構成の明示的なバージョンを示しています。

明示的な WebFlux セキュリティ構成
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {

	@Bean
	public MapReactiveUserDetailsService userDetailsService() {
		UserDetails user = User.withDefaultPasswordEncoder()
			.username("user")
			.password("user")
			.roles("USER")
			.build();
		return new MapReactiveUserDetailsService(user);
	}

	@Bean
	public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
		http
			.authorizeExchange((authorize) -> authorize
			    .anyExchange().authenticated()
			)
			.httpBasic(withDefaults())
			.formLogin(withDefaults());
		return http.build();
	}
}
import org.springframework.security.config.web.server.invoke

@Configuration
@EnableWebFluxSecurity
class HelloWebfluxSecurityConfig {

    @Bean
    fun userDetailsService(): ReactiveUserDetailsService {
        val userDetails = User.withDefaultPasswordEncoder()
                .username("user")
                .password("user")
                .roles("USER")
                .build()
        return MapReactiveUserDetailsService(userDetails)
    }

    @Bean
    fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http {
            authorizeExchange {
                authorize(anyExchange, authenticated)
            }
            formLogin { }
            httpBasic { }
        }
    }
}
IDE がメソッドを常に自動インポートするとは限らず、コンパイルの問題が発生する可能性があるため、クラスで Kotlin DSL を有効にするには、必ず org.springframework.security.config.web.server.invoke 関数をインポートしてください。

この構成は、最小構成と同じものをすべて明示的に設定します。ここから、デフォルトをより簡単に変更できます。

 config/src/test/ ディレクトリの EnableWebFluxSecurity  [GitHub] (英語) を検索すると、単体テストで明示的な構成の例をさらに見つけることができます。

複数のチェーンサポート

複数の SecurityWebFilterChain インスタンスを構成して、RequestMatcher インスタンスごとに構成を分離できます。

例: /api で始まる URL の構成を分離できます:

  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
static class MultiSecurityHttpConfig {

    @Order(Ordered.HIGHEST_PRECEDENCE)                                                      (1)
    @Bean
    SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http) {
        http
            .securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/**"))      (2)
            .authorizeExchange((authorize) -> authorize
                .anyExchange().authenticated()
            )
            .oauth2ResourceServer(OAuth2ResourceServerSpec::jwt);                           (3)
        return http.build();
    }

    @Bean
    SecurityWebFilterChain webHttpSecurity(ServerHttpSecurity http) {                       (4)
        http
            .authorizeExchange((authorize) -> authorize
                .anyExchange().authenticated()
            )
            .httpBasic(withDefaults());                                                     (5)
        return http.build();
    }

    @Bean
    ReactiveUserDetailsService userDetailsService() {
        return new MapReactiveUserDetailsService(
                PasswordEncodedUser.user(), PasswordEncodedUser.admin());
    }

}
import org.springframework.security.config.web.server.invoke

@Configuration
@EnableWebFluxSecurity
open class MultiSecurityHttpConfig {
    @Order(Ordered.HIGHEST_PRECEDENCE)                                                      (1)
    @Bean
    open fun apiHttpSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http {
            securityMatcher(PathPatternParserServerWebExchangeMatcher("/api/**"))           (2)
            authorizeExchange {
                authorize(anyExchange, authenticated)
            }
            oauth2ResourceServer {
                jwt { }                                                                     (3)
            }
        }
    }

    @Bean
    open fun webHttpSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {            (4)
        return http {
            authorizeExchange {
                authorize(anyExchange, authenticated)
            }
            httpBasic { }                                                                   (5)
        }
    }

    @Bean
    open fun userDetailsService(): ReactiveUserDetailsService {
        return MapReactiveUserDetailsService(
            PasswordEncodedUser.user(), PasswordEncodedUser.admin()
        )
    }
}
1@Order を使用して SecurityWebFilterChain を構成し、Spring Security が最初に検討する必要がある SecurityWebFilterChain を指定します
2PathPatternParserServerWebExchangeMatcher を使用して、この SecurityWebFilterChain が /api/ で始まる URL パスにのみ適用されることを記述します
3/api/** エンドポイントに使用される認証メカニズムを指定します
4 他のすべての URL と一致するように、優先順位の低い SecurityWebFilterChain の別のインスタンスを作成します
5 アプリケーションの残りの部分で使用される認証メカニズムを指定します

Spring Security は、リクエストごとに 1 つの SecurityWebFilterChain @Bean を選択します。securityMatcher 定義の順番でリクエストに一致します。

この場合、URL パスが /api で始まる場合、Spring Security は apiHttpSecurity を使用することを意味します。URL が /api で始まらない場合、Spring Security はデフォルトで webHttpSecurity になります。これには、任意のリクエストに一致する暗黙の securityMatcher があります。

モジュラー ServerHttpSecurity 構成

多くのユーザーは、Spring Security の設定を一元管理することを好み、SecurityWebFilterChain Bean 宣言内で設定することを選択します。しかし、設定をモジュール化したい場合もあります。これは、以下の方法で実現できます。

カスタマイザー <ServerHttpSecurity> Bean

セキュリティ構成をモジュール化したい場合は、Customizer<ServerHttpSecurity> Bean にロジックを配置できます。例: 次の構成では、すべての ServerHttpSecurity インスタンスが次のように構成されます。

  • Java

  • Kotlin

@Bean
Customizer<ServerHttpSecurity> httpSecurityCustomizer() {
	return (http) -> http
		.headers((headers) -> headers
			.contentSecurityPolicy((csp) -> csp
				(1)
				.policyDirectives("object-src 'none'")
			)
		)
		(2)
		.redirectToHttps(Customizer.withDefaults());
}
@Bean
fun httpSecurityCustomizer(): Customizer<ServerHttpSecurity> {
    return Customizer { http -> http
        .headers { headers -> headers
            .contentSecurityPolicy { csp -> csp
                (1)
                .policyDirectives("object-src 'none'")
            }
        }
        (2)
        .redirectToHttps(Customizer.withDefaults())
    }
}

トップレベル ServerHttpSecurity カスタマイザー Bean

セキュリティ構成をさらにモジュール化したい場合は、Spring Security によってトップレベルの HttpSecurity および Customizer Bean が自動的に適用されます。

最上位の HttpSecurity 型または Customizer 型は、public HttpSecurity.*(Customizer<T>) に一致する任意の Customizer<T> として要約できます。これは、HttpSecurity (Javadoc) の public メソッドの単一引数である任意の Customizer<T> に相当します。

A few examples can help to clarify. If Customizer<ContentTypeOptionsConfig> is published as a Bean, it will not be be automatically applied because it is an argument to HeadersConfigurer.contentTypeOptions(Customizer) (Javadoc) which is not a method defined on HttpSecurity. However, if Customizer<HeadersConfigurer<HttpSecurity>> is published as a Bean, it will be automatically applied because it is an argument to HttpSecurity.headers(Customizer) (Javadoc)

For example, the following configuration will ensure that the コンテンツセキュリティポリシー is set to object-src 'none':

  • Java

  • Kotlin

@Bean
Customizer<ServerHttpSecurity.HeaderSpec> headersSecurity() {
	return (headers) -> headers
		.contentSecurityPolicy((csp) -> csp
			(1)
			.policyDirectives("object-src 'none'")
		);
}
@Bean
fun headersSecurity(): Customizer<ServerHttpSecurity.HeaderSpec> {
    return Customizer { headers -> headers
        .contentSecurityPolicy { csp -> csp
            (1)
            .policyDirectives("object-src 'none'")
        }
    }
}

Customizer Bean Ordering

First each Customizer<HttpSecurity> Bean is applied using ObjectProvider#orderedStream() (Javadoc) . This means that if there are multiple Customizer<HttpSecurity> Beans, the @Order (Javadoc) annotation can be added to the Bean definitions to control the ordering.

Next every トップレベル HttpSecurity カスタマイザー Bean type is looked up and each is is applied using ObjectProvider#orderedStream(). If there is are two Customizer<HeadersConfigurer<HttpSecurity>> beans and two Customizer<HttpsRedirectConfigurer<HttpSecurity>> instances, the order that each Customizer type is invoked is undefined. However, the order that each instance of Customizer<HttpsRedirectConfigurer<HttpSecurity>> is defined by ObjectProvider#orderedStream() and can be controlled using @Order on the Bean the definitions.

Finally, the HttpSecurity Bean is injected as a Bean. All Customizer instances are applied before the HttpSecurity Bean is created. This allows overriding the customizations provided by the Customizer Beans.

You can find an example below that illustrates the ordering:

  • Java

  • Kotlin

@Bean (4)
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
	http
		.authorizeExchange((exchange) -> exchange
			.anyExchange().authenticated()
		);
	return http.build();
}

@Bean
@Order(Ordered.LOWEST_PRECEDENCE) (2)
Customizer<ServerHttpSecurity> userAuthorization() {
	return (http) -> http
		.authorizeExchange((exchange) -> exchange
			.pathMatchers("/users/**").hasRole("USER")
		);
}

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
Customizer<ServerHttpSecurity> adminAuthorization() {
	return (http) -> http
		.authorizeExchange((exchange) -> exchange
			.pathMatchers("/admins/**").hasRole("ADMIN")
		);
}

(3)

@Bean
Customizer<ServerHttpSecurity.HeaderSpec> contentSecurityPolicy() {
	return (headers) -> headers
		.contentSecurityPolicy((csp) -> csp
			.policyDirectives("object-src 'none'")
		);
}

@Bean
Customizer<ServerHttpSecurity.HeaderSpec> contentTypeOptions() {
	return (headers) -> headers
		.contentTypeOptions(Customizer.withDefaults());
}

@Bean
Customizer<ServerHttpSecurity.HttpsRedirectSpec> httpsRedirect() {
	return Customizer.withDefaults();
}
@Bean (4)
fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
    http
        .authorizeExchange({ exchanges -> exchanges
            .anyExchange().authenticated()
        })
    return http.build()
}

@Bean
@Order(Ordered.LOWEST_PRECEDENCE)  (2)
fun userAuthorization(): Customizer<ServerHttpSecurity> {
    return Customizer { http -> http
        .authorizeExchange { exchanges -> exchanges
            .pathMatchers("/users/**").hasRole("USER")
        }
    }
}

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
fun adminAuthorization(): Customizer<ServerHttpSecurity> {
    return ThrowingCustomizer { http -> http
        .authorizeExchange { exchanges -> exchanges
            .pathMatchers("/admins/**").hasRole("ADMIN")
        }
    }
}

(3)

@Bean
fun contentSecurityPolicy(): Customizer<ServerHttpSecurity.HeaderSpec> {
    return Customizer { headers -> headers
        .contentSecurityPolicy { csp -> csp
            .policyDirectives("object-src 'none'")
        }
    }
}

@Bean
fun contentTypeOptions(): Customizer<ServerHttpSecurity.HeaderSpec> {
    return Customizer { headers -> headers
        .contentTypeOptions(Customizer.withDefaults())
    }
}

@Bean
fun httpsRedirect(): Customizer<ServerHttpSecurity.HttpsRedirectSpec> {
    return Customizer.withDefaults()
}
1First all Customizer<HttpSecurity> instances are applied. The adminAuthorization Bean has the highest @Order so it is applied first. If there are no @Order annotations on the Customizer<HttpSecurity> Beans or the @Order annotations had the same value, then the order that the Customizer<HttpSecurity> instances are applied is undefined.
2The userAuthorization is applied next due to being an instance of Customizer<HttpSecurity>
3The order that the Customizer types are undefined. In this example, the order of contentSecurityPolicycontentTypeOptionshttpsRedirect are undefined. If @Order(Ordered.HIGHEST_PRECEDENCE) was added to contentTypeOptions, then we would know that contentTypeOptions is before contentSecurityPolicy (they are the same type), but we do not know if httpsRedirect is before or after the Customizer<HeadersConfigurer<HttpSecurity>> Beans.
4After all of the Customizer Beans are applied, the HttpSecurity is passed in as a Bean.