Add Modular Spring Security Configuration

Closes gh-16258
This commit is contained in:
Rob Winch 2025-08-20 09:32:34 -05:00
parent 5c5efc9092
commit a8f045eb50
No known key found for this signature in database
49 changed files with 3207 additions and 0 deletions

View File

@ -0,0 +1,52 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config;
/**
* A {@link Customizer} that allows invocation of code that throws a checked exception.
*
* @param <T> The type of input.
*/
@FunctionalInterface
public interface ThrowingCustomizer<T> extends Customizer<T> {
/**
* Default {@link Customizer#customize(Object)} that wraps any thrown checked
* exceptions (by default in a {@link RuntimeException}).
* @param t the object to customize
*/
default void customize(T t) {
try {
customizeWithException(t);
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Performs the customization on the given object, possibly throwing a checked
* exception.
* @param t the object to customize
* @throws Exception on error
*/
void customizeWithException(T t) throws Exception;
}

View File

@ -16,19 +16,24 @@
package org.springframework.security.config.annotation.web.configuration; package org.springframework.security.config.annotation.web.configuration;
import java.lang.reflect.Modifier;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@ -46,6 +51,7 @@ import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.ThrowingSupplier; import org.springframework.util.function.ThrowingSupplier;
import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy; import org.springframework.web.accept.HeaderContentNegotiationStrategy;
@ -131,6 +137,8 @@ class HttpSecurityConfiguration {
// @formatter:on // @formatter:on
applyCorsIfAvailable(http); applyCorsIfAvailable(http);
applyDefaultConfigurers(http); applyDefaultConfigurers(http);
applyHttpSecurityCustomizers(this.context, http);
applyTopLevelCustomizers(this.context, http);
return http; return http;
} }
@ -160,6 +168,73 @@ class HttpSecurityConfiguration {
} }
} }
/**
* Applies all {@code Customizer<HttpSecurity>} Bean instances to the
* {@link HttpSecurity} instance.
* @param applicationContext the {@link ApplicationContext} to lookup Bean instances
* @param http the {@link HttpSecurity} to apply the Beans to.
*/
private void applyHttpSecurityCustomizers(ApplicationContext applicationContext, HttpSecurity http) {
ResolvableType httpSecurityCustomizerType = ResolvableType.forClassWithGenerics(Customizer.class,
HttpSecurity.class);
ObjectProvider<Customizer<HttpSecurity>> customizerProvider = this.context
.getBeanProvider(httpSecurityCustomizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
customizer.customize(http)
);
// @formatter:on
}
/**
* Applies all {@link Customizer} Beans to {@link HttpSecurity}. For each public,
* non-static method in HttpSecurity that accepts a Customizer
* <ul>
* <li>Use the {@link MethodParameter} (this preserves generics) to resolve all Beans
* for that type</li>
* <li>For each {@link Customizer} Bean invoke the {@link java.lang.reflect.Method}
* with the {@link Customizer} Bean as the argument</li>
* </ul>
* @param context the {@link ApplicationContext}
* @param http the {@link HttpSecurity}
* @throws Exception
*/
private void applyTopLevelCustomizers(ApplicationContext context, HttpSecurity http) {
ReflectionUtils.MethodFilter isCustomizerMethod = (method) -> {
if (Modifier.isStatic(method.getModifiers())) {
return false;
}
if (!Modifier.isPublic(method.getModifiers())) {
return false;
}
if (!method.canAccess(http)) {
return false;
}
if (method.getParameterCount() != 1) {
return false;
}
if (method.getParameterTypes()[0] == Customizer.class) {
return true;
}
return false;
};
ReflectionUtils.MethodCallback invokeWithEachCustomizerBean = (customizerMethod) -> {
MethodParameter customizerParameter = new MethodParameter(customizerMethod, 0);
ResolvableType customizerType = ResolvableType.forMethodParameter(customizerParameter);
ObjectProvider<?> customizerProvider = context.getBeanProvider(customizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
ReflectionUtils.invokeMethod(customizerMethod, http, customizer)
);
// @formatter:on
};
ReflectionUtils.doWithMethods(HttpSecurity.class, invokeWithEachCustomizerBean, isCustomizerMethod);
}
private Map<Class<?>, Object> createSharedObjects() { private Map<Class<?>, Object> createSharedObjects() {
Map<Class<?>, Object> sharedObjects = new HashMap<>(); Map<Class<?>, Object> sharedObjects = new HashMap<>();
sharedObjects.put(ApplicationContext.class, this.context); sharedObjects.put(ApplicationContext.class, this.context);

View File

@ -16,6 +16,7 @@
package org.springframework.security.config.annotation.web.reactive; package org.springframework.security.config.annotation.web.reactive;
import java.lang.reflect.Modifier;
import java.util.Map; import java.util.Map;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@ -28,10 +29,13 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker; import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
@ -40,6 +44,7 @@ import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver; import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.web.reactive.result.method.annotation.CurrentSecurityContextArgumentResolver; import org.springframework.security.web.reactive.result.method.annotation.CurrentSecurityContextArgumentResolver;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
@ -154,6 +159,83 @@ class ServerHttpSecurityConfiguration {
@Bean(HTTPSECURITY_BEAN_NAME) @Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype") @Scope("prototype")
ServerHttpSecurity httpSecurity(ApplicationContext context) {
ServerHttpSecurity http = httpSecurity();
applyServerHttpSecurityCustomizers(context, http);
applyTopLevelBeanCustomizers(context, http);
return http;
}
/**
* Applies all {@code Custmizer<ServerHttpSecurity>} Beans to
* {@link ServerHttpSecurity}.
* @param context the {@link ApplicationContext}
* @param http the {@link ServerHttpSecurity}
* @throws Exception
*/
private void applyServerHttpSecurityCustomizers(ApplicationContext context, ServerHttpSecurity http) {
ResolvableType httpSecurityCustomizerType = ResolvableType.forClassWithGenerics(Customizer.class,
ServerHttpSecurity.class);
ObjectProvider<Customizer<ServerHttpSecurity>> customizerProvider = context
.getBeanProvider(httpSecurityCustomizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
customizer.customize(http)
);
// @formatter:on
}
/**
* Applies all {@link Customizer} Beans to top level {@link ServerHttpSecurity}
* method.
*
* For each public, non-static method in ServerHttpSecurity that accepts a Customizer
* <ul>
* <li>Use the {@link MethodParameter} (this preserves generics) to resolve all Beans
* for that type</li>
* <li>For each {@link Customizer} Bean invoke the {@link java.lang.reflect.Method}
* with the {@link Customizer} Bean as the argument</li>
* </ul>
* @param context the {@link ApplicationContext}
* @param http the {@link ServerHttpSecurity}
* @throws Exception
*/
private void applyTopLevelBeanCustomizers(ApplicationContext context, ServerHttpSecurity http) {
ReflectionUtils.MethodFilter isCustomizerMethod = (method) -> {
if (Modifier.isStatic(method.getModifiers())) {
return false;
}
if (!Modifier.isPublic(method.getModifiers())) {
return false;
}
if (!method.canAccess(http)) {
return false;
}
if (method.getParameterCount() != 1) {
return false;
}
if (method.getParameterTypes()[0] == Customizer.class) {
return true;
}
return false;
};
ReflectionUtils.MethodCallback invokeWithEachCustomizerBean = (customizerMethod) -> {
MethodParameter customizerParameter = new MethodParameter(customizerMethod, 0);
ResolvableType customizerType = ResolvableType.forMethodParameter(customizerParameter);
ObjectProvider<?> customizerProvider = context.getBeanProvider(customizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
ReflectionUtils.invokeMethod(customizerMethod, http, customizer)
);
// @formatter:on
};
ReflectionUtils.doWithMethods(ServerHttpSecurity.class, invokeWithEachCustomizerBean, isCustomizerMethod);
}
ServerHttpSecurity httpSecurity() { ServerHttpSecurity httpSecurity() {
ContextAwareServerHttpSecurity http = new ContextAwareServerHttpSecurity(); ContextAwareServerHttpSecurity http = new ContextAwareServerHttpSecurity();
// @formatter:off // @formatter:off

View File

@ -1316,6 +1316,10 @@ public class ServerHttpSecurity {
return this.context.getBeanNamesForType(beanClass); return this.context.getBeanNamesForType(beanClass);
} }
ApplicationContext getApplicationContext() {
return this.context;
}
protected void setApplicationContext(ApplicationContext applicationContext) throws BeansException { protected void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext; this.context = applicationContext;
} }

View File

@ -18,14 +18,22 @@ package org.springframework.security.config.annotation.web
import jakarta.servlet.Filter import jakarta.servlet.Filter
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import org.springframework.beans.factory.ObjectProvider
import org.springframework.context.ApplicationContext import org.springframework.context.ApplicationContext
import org.springframework.core.MethodParameter
import org.springframework.core.ResolvableType
import org.springframework.core.annotation.AnnotationUtils
import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.SecurityConfigurerAdapter import org.springframework.security.config.annotation.SecurityConfigurerAdapter
import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
import org.springframework.security.web.DefaultSecurityFilterChain import org.springframework.security.web.DefaultSecurityFilterChain
import org.springframework.security.web.util.matcher.RequestMatcher import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.util.ReflectionUtils
import java.lang.reflect.Method
import java.lang.reflect.Modifier
/** /**
* Configures [HttpSecurity] using a [HttpSecurity Kotlin DSL][HttpSecurityDsl]. * Configures [HttpSecurity] using a [HttpSecurity Kotlin DSL][HttpSecurityDsl].
@ -77,6 +85,117 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
var authenticationManager: AuthenticationManager? = null var authenticationManager: AuthenticationManager? = null
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java) val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
init {
applyFunction1HttpSecurityDslBeans(this.context, this)
applyTopLevelFunction1SecurityDslBeans(this.context, this)
}
/**
* Applies all `Function1<HttpSecurity,Unit>` Beans which
* allows exposing the DSL as Beans to be applied.
*
* ```
* @Bean
* fun httpSecurityDslBean(): HttpSecurityDsl.() -> Unit {
* return {
* headers {
* contentSecurityPolicy {
* policyDirectives = "object-src 'none'"
* }
* }
* redirectToHttps { }
* }
* }
* ```
*/
private fun applyFunction1HttpSecurityDslBeans(context: ApplicationContext, http: HttpSecurityDsl) : Unit {
val httpSecurityDslFnType = ResolvableType.forClassWithGenerics(Function1::class.java,
HttpSecurityDsl::class.java, Unit::class.java)
val httpSecurityDslFnProvider = context
.getBeanProvider<Function1<HttpSecurityDsl,Unit>>(httpSecurityDslFnType)
// @formatter:off
httpSecurityDslFnProvider.orderedStream().forEach { fn -> fn.invoke(http) }
// @formatter:on
}
/**
* Applies all `Function1<T,Unit>` Beans such that `T` is a top level
* DSL on `HttpSecurityDsl`. This allows exposing the top level
* DSLs as Beans to be applied.
*
*
* ```
* @Bean
* fun httpSecurityCustomizer(): ThrowingCustomizer<HttpSecurity> {
* return ThrowingCustomizer { http -> http
* .headers { headers -> headers
* .contentSecurityPolicy { csp -> csp
* .policyDirectives("object-src 'none'")
* }
* }
* .redirectToHttps(Customizer.withDefaults())
* }
* }
* ```
*
* @param context the [ApplicationContext]
* @param http the [HttpSecurity]
* @throws Exception
*/
private fun applyTopLevelFunction1SecurityDslBeans(context: ApplicationContext, http: HttpSecurityDsl) {
val isCustomizerMethod = ReflectionUtils.MethodFilter { method: Method ->
if (Modifier.isStatic(method.modifiers)) {
return@MethodFilter false
}
if (!Modifier.isPublic(method.modifiers)) {
return@MethodFilter false
}
if (!method.canAccess(http)) {
return@MethodFilter false
}
if (method.parameterCount != 1) {
return@MethodFilter false
}
return@MethodFilter extractDslType(method) != null
}
val invokeWithEachDslBean = ReflectionUtils.MethodCallback { dslMethod: Method ->
val dslFunctionType = firstMethodResolvableType(dslMethod)!!
val dslFunctionProvider: ObjectProvider<*> = context.getBeanProvider<Any>(dslFunctionType)
// @formatter:off
dslFunctionProvider.orderedStream().forEach {customizer: Any -> ReflectionUtils.invokeMethod(dslMethod, http, customizer)}
}
ReflectionUtils.doWithMethods(HttpSecurityDsl::class.java, invokeWithEachDslBean, isCustomizerMethod)
}
/**
* From a `Method` with the first argument `Function<T,Unit>` return `T` or `null`
* if the first argument is not a `Function`.
* @return From a `Method` with the first argument `Function<T,Unit>` return `T`.
*/
private fun extractDslType(method: Method): ResolvableType? {
val functionType = firstMethodResolvableType(method)
if (!Function::class.java.isAssignableFrom(functionType.toClass())) {
return null
}
val functionInputType = functionType.getGeneric(0)
val securityMarker = AnnotationUtils.findAnnotation(functionInputType.toClass(), SecurityMarker::class.java)
val isSecurityDsl = securityMarker != null
if (!isSecurityDsl) {
return null
}
return functionInputType
}
private fun firstMethodResolvableType(method: Method): ResolvableType {
val parameter = MethodParameter(
method, 0
)
return ResolvableType.forMethodParameter(parameter)
}
/** /**
* Applies a [SecurityConfigurerAdapter] to this [HttpSecurity] * Applies a [SecurityConfigurerAdapter] to this [HttpSecurity]
* *

View File

@ -16,13 +16,23 @@
package org.springframework.security.config.web.server package org.springframework.security.config.web.server
import org.springframework.beans.factory.ObjectProvider
import org.springframework.context.ApplicationContext
import org.springframework.core.MethodParameter
import org.springframework.core.ResolvableType
import org.springframework.core.annotation.AnnotationUtils
import org.springframework.security.authentication.ReactiveAuthenticationManager import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
import org.springframework.security.web.server.SecurityWebFilterChain import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.context.ServerSecurityContextRepository import org.springframework.security.web.server.context.ServerSecurityContextRepository
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
import org.springframework.util.ReflectionUtils
import org.springframework.web.server.ServerWebExchange import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter import org.springframework.web.server.WebFilter
import java.lang.reflect.Method
import java.lang.reflect.Modifier
/** /**
* Configures [ServerHttpSecurity] using a [ServerHttpSecurity Kotlin DSL][ServerHttpSecurityDsl]. * Configures [ServerHttpSecurity] using a [ServerHttpSecurity Kotlin DSL][ServerHttpSecurityDsl].
@ -68,6 +78,115 @@ class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val in
var authenticationManager: ReactiveAuthenticationManager? = null var authenticationManager: ReactiveAuthenticationManager? = null
var securityContextRepository: ServerSecurityContextRepository? = null var securityContextRepository: ServerSecurityContextRepository? = null
init {
applyFunction1HttpSecurityDslBeans(this.http.applicationContext, this)
applyTopLevelFunction1SecurityDslBeans(this.http.applicationContext, this)
}
/**
* Applies all `Function1<ServerHttpSecurityDsl,Unit>` Beans which
* allows exposing the DSL as Beans to be applied.
*
* ```
* @Bean
* @Order(Ordered.LOWEST_PRECEDENCE)
* fun userAuthorization(): ServerHttpSecurityDsl.() -> Unit {
* // @formatter:off
* return {
* authorizeExchange {
* authorize("/user/profile", hasRole("USER"))
* }
* }
* // @formatter:on
* }
* ```
*/
private fun applyFunction1HttpSecurityDslBeans(context: ApplicationContext, http: ServerHttpSecurityDsl) : Unit {
val httpSecurityDslFnType = ResolvableType.forClassWithGenerics(Function1::class.java,
ServerHttpSecurityDsl::class.java, Unit::class.java)
val httpSecurityDslFnProvider = context
.getBeanProvider<Function1<ServerHttpSecurityDsl,Unit>>(httpSecurityDslFnType)
// @formatter:off
httpSecurityDslFnProvider.orderedStream().forEach { fn -> fn.invoke(http) }
// @formatter:on
}
/**
* Applies all `Function1<T,Unit>` Beans such that `T` is a top level
* DSL on `ServerHttpSecurityDsl`. This allows exposing the top level
* DSLs as Beans to be applied.
*
* ```
* @Bean
* fun headersSecurity(): Customizer<ServerHttpSecurity.HeaderSpec> {
* // @formatter:off
* return Customizer { headers -> headers
* .contentSecurityPolicy { csp -> csp
* .policyDirectives("object-src 'none'")
* }
* }
* // @formatter:on
* }
* ```
*
* @param context the [ApplicationContext]
* @param http the [HttpSecurity]
* @throws Exception
*/
private fun applyTopLevelFunction1SecurityDslBeans(context: ApplicationContext, http: ServerHttpSecurityDsl) {
val isCustomizerMethod = ReflectionUtils.MethodFilter { method: Method ->
if (Modifier.isStatic(method.modifiers)) {
return@MethodFilter false
}
if (!Modifier.isPublic(method.modifiers)) {
return@MethodFilter false
}
if (!method.canAccess(http)) {
return@MethodFilter false
}
if (method.parameterCount != 1) {
return@MethodFilter false
}
return@MethodFilter extractDslType(method) != null
}
val invokeWithEachDslBean = ReflectionUtils.MethodCallback { dslMethod: Method ->
val dslFunctionType = firstMethodResolvableType(dslMethod)!!
val dslFunctionProvider: ObjectProvider<*> = context.getBeanProvider<Any>(dslFunctionType)
// @formatter:off
dslFunctionProvider.orderedStream().forEach {customizer: Any -> ReflectionUtils.invokeMethod(dslMethod, http, customizer)}
}
ReflectionUtils.doWithMethods(ServerHttpSecurityDsl::class.java, invokeWithEachDslBean, isCustomizerMethod)
}
/**
* From a `Method` with the first argument `Function<T,Unit>` return `T` or `null`
* if the first argument is not a `Function`.
* @return From a `Method` with the first argument `Function<T,Unit>` return `T`.
*/
private fun extractDslType(method: Method): ResolvableType? {
val functionType = firstMethodResolvableType(method)
if (!Function::class.java.isAssignableFrom(functionType.toClass())) {
return null
}
val functionInputType = functionType.getGeneric(0)
val securityMarker = AnnotationUtils.findAnnotation(functionInputType.toClass(), ServerSecurityMarker::class.java)
val isSecurityDsl = securityMarker != null
if (!isSecurityDsl) {
return null
}
return functionInputType
}
private fun firstMethodResolvableType(method: Method): ResolvableType {
val parameter = MethodParameter(
method, 0
)
return ResolvableType.forMethodParameter(parameter)
}
/** /**
* Allows configuring the [ServerHttpSecurity] to only be invoked when matching the * Allows configuring the [ServerHttpSecurity] to only be invoked when matching the
* provided [ServerWebExchangeMatcher]. * provided [ServerWebExchangeMatcher].

View File

@ -28,14 +28,20 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
@ -54,6 +60,7 @@ import org.springframework.security.config.annotation.SecurityContextChangedList
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer; import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
@ -82,12 +89,15 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.withSettings;
import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
@ -425,6 +435,77 @@ public class HttpSecurityConfigurationTests {
.andExpectAll(status().isFound(), redirectedUrl("/"), authenticated()); .andExpectAll(status().isFound(), redirectedUrl("/"), authenticated());
} }
@Test
void authorizeHttpRequestsCustomizerBean() throws Exception {
this.spring.register(AuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
.getContext()
.getBean("authorizeRequests", Customizer.class);
verify(authorizeRequests).customize(any());
}
@Test
void multiAuthorizeHttpRequestsCustomizerBean() throws Exception {
this.spring.register(MultiAuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests0 = this.spring
.getContext()
.getBean("authorizeRequests0", Customizer.class);
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
.getContext()
.getBean("authorizeRequests", Customizer.class);
InOrder inOrder = Mockito.inOrder(authorizeRequests0, authorizeRequests);
ArgumentCaptor<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> arg0 = ArgumentCaptor
.forClass(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry.class);
ArgumentCaptor<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> arg1 = ArgumentCaptor
.forClass(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry.class);
inOrder.verify(authorizeRequests0).customize(arg0.capture());
inOrder.verify(authorizeRequests).customize(arg1.capture());
}
@Test
void disableAuthorizeHttpRequestsCustomizerBean() throws Exception {
this.spring.register(AuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
.getContext()
.getBean("authorizeRequests", Customizer.class);
verify(authorizeRequests).customize(any());
}
@Test
void httpSecurityCustomizerBean() throws Exception {
this.spring.register(HttpSecurityCustomizerBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<HttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
ArgumentCaptor<HttpSecurity> arg0 = ArgumentCaptor.forClass(HttpSecurity.class);
verify(httpSecurityCustomizer).customize(arg0.capture());
}
@Test
void multiHttpSecurityCustomizerBean() throws Exception {
this.spring.register(MultiHttpSecurityCustomizerBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<HttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
Customizer<HttpSecurity> httpSecurityCustomizer0 = this.spring.getContext()
.getBean("httpSecurityCustomizer0", Customizer.class);
InOrder inOrder = Mockito.inOrder(httpSecurityCustomizer0, httpSecurityCustomizer);
ArgumentCaptor<HttpSecurity> arg0 = ArgumentCaptor.forClass(HttpSecurity.class);
ArgumentCaptor<HttpSecurity> arg1 = ArgumentCaptor.forClass(HttpSecurity.class);
inOrder.verify(httpSecurityCustomizer0).customize(arg0.capture());
inOrder.verify(httpSecurityCustomizer).customize(arg1.capture());
}
@RestController @RestController
static class NameController { static class NameController {
@ -785,6 +866,134 @@ public class HttpSecurityConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableWebMvc
static class AuthorizeRequestsBeanConfiguration {
@Bean
SecurityFilterChain noAuthorizeSecurity(HttpSecurity http) throws Exception {
http.httpBasic(withDefaults());
return http.build();
}
@Bean
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests()
throws Exception {
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authz = mock(
Customizer.class, withSettings().name("authz"));
// prevent validation errors of no authorization rules being defined
willAnswer(((invocation) -> {
AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry requests = invocation
.getArgument(0);
requests.anyRequest().authenticated();
return null;
})).given(authz).customize(any());
return authz;
}
@RestController
static class PublicController {
@GetMapping("/public")
String permitAll() {
return "public";
}
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableWebMvc
static class DisableAuthorizeRequestsBeanConfiguration {
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
http.httpBasic(withDefaults());
// @formatter:off
http.authorizeHttpRequests((requests) -> requests
.anyRequest().permitAll()
);
// @formatter:on
return http.build();
}
@Bean
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests()
throws Exception {
// @formatter:off
return (requests) -> requests
.anyRequest().denyAll();
// @formatter:on
}
@RestController
static class PublicController {
@GetMapping("/public")
String permitAll() {
return "public";
}
}
}
@Configuration(proxyBeanMethods = false)
@Import(AuthorizeRequestsBeanConfiguration.class)
static class MultiAuthorizeRequestsBeanConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests0()
throws Exception {
return mock(Customizer.class, withSettings().name("authz0"));
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableWebMvc
static class HttpSecurityCustomizerBeanConfiguration {
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
http.httpBasic(withDefaults());
return http.build();
}
@Bean
static Customizer<HttpSecurity> httpSecurityCustomizer() {
return mock(Customizer.class, withSettings().name("httpSecurityCustomizer"));
}
@RestController
static class PublicController {
@GetMapping("/public")
String permitAll() {
return "public";
}
}
}
@Configuration(proxyBeanMethods = false)
@Import(HttpSecurityCustomizerBeanConfiguration.class)
static class MultiHttpSecurityCustomizerBeanConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
static Customizer<HttpSecurity> httpSecurityCustomizer0() throws Exception {
return mock(Customizer.class, withSettings().name("httpSecurityCustomizer0"));
}
}
private static class TestCompromisedPasswordChecker implements CompromisedPasswordChecker { private static class TestCompromisedPasswordChecker implements CompromisedPasswordChecker {
@Override @Override

View File

@ -29,12 +29,17 @@ import io.micrometer.observation.ObservationRegistry;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.password.CompromisedPasswordDecision; import org.springframework.security.authentication.password.CompromisedPasswordDecision;
@ -46,6 +51,7 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
@ -267,6 +273,47 @@ public class ServerHttpSecurityConfigurationTests {
assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after"); assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after");
} }
@Test
void authorizeExchangeCustomizerBean() {
this.spring.register(AuthorizeExchangeCustomizerBeanConfig.class).autowire();
Customizer<AuthorizeExchangeSpec> authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class);
ArgumentCaptor<AuthorizeExchangeSpec> arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class);
verify(authzCustomizer).customize(arg0.capture());
}
@Test
void multiAuthorizeExchangeCustomizerBean() {
this.spring.register(MultiAuthorizeExchangeCustomizerBeanConfig.class).autowire();
Customizer<AuthorizeExchangeSpec> authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class);
ArgumentCaptor<AuthorizeExchangeSpec> arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class);
verify(authzCustomizer).customize(arg0.capture());
}
@Test
void serverHttpSecurityCustomizerBean() {
this.spring.register(ServerHttpSecurityCustomizerConfig.class).autowire();
Customizer<ServerHttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
ArgumentCaptor<ServerHttpSecurity> arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class);
verify(httpSecurityCustomizer).customize(arg0.capture());
}
@Test
void multiServerHttpSecurityCustomizerBean() {
this.spring.register(MultiServerHttpSecurityCustomizerConfig.class).autowire();
Customizer<ServerHttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
Customizer<ServerHttpSecurity> httpSecurityCustomizer0 = this.spring.getContext()
.getBean("httpSecurityCustomizer0", Customizer.class);
InOrder inOrder = Mockito.inOrder(httpSecurityCustomizer0, httpSecurityCustomizer);
ArgumentCaptor<ServerHttpSecurity> arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class);
inOrder.verify(httpSecurityCustomizer0).customize(arg0.capture());
inOrder.verify(httpSecurityCustomizer).customize(arg0.capture());
}
@Configuration @Configuration
static class SubclassConfig extends ServerHttpSecurityConfiguration { static class SubclassConfig extends ServerHttpSecurityConfiguration {
@ -474,4 +521,64 @@ public class ServerHttpSecurityConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@EnableWebFluxSecurity
@Import(UserDetailsConfig.class)
static class AuthorizeExchangeCustomizerBeanConfig {
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http.build();
}
@Bean
static Customizer<AuthorizeExchangeSpec> authz() {
return mock(Customizer.class);
}
}
@Configuration(proxyBeanMethods = false)
@Import(AuthorizeExchangeCustomizerBeanConfig.class)
static class MultiAuthorizeExchangeCustomizerBeanConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
Customizer<AuthorizeExchangeSpec> authz0() {
return mock(Customizer.class);
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@EnableWebFluxSecurity
@Import(UserDetailsConfig.class)
static class ServerHttpSecurityCustomizerConfig {
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http.build();
}
@Bean
static Customizer<ServerHttpSecurity> httpSecurityCustomizer() {
return mock(Customizer.class);
}
}
@Configuration(proxyBeanMethods = false)
@Import(ServerHttpSecurityCustomizerConfig.class)
static class MultiServerHttpSecurityCustomizerConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
static Customizer<ServerHttpSecurity> httpSecurityCustomizer0() {
return mock(Customizer.class);
}
}
} }

View File

@ -623,5 +623,38 @@ class HttpSecurityDslTests {
} }
@Test
fun `HTTP security when Dsl Bean`() {
this.spring.register(DslBeanConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header {
string("Content-Security-Policy", "object-src 'none'")
}
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
open class DslBeanConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
httpBasic { }
}
return http.build()
}
@Bean
open fun headersDsl(): HeadersDsl.() -> Unit {
return {
contentSecurityPolicy {
policyDirectives = "object-src 'none'"
}
}
}
}
} }

View File

@ -242,3 +242,72 @@ It matches the requests in order by the `securityMatcher` definition.
In this case, that means that, if the URL path starts with `/api`, Spring Security uses `apiHttpSecurity`. In this case, that means that, if the URL path starts with `/api`, Spring Security uses `apiHttpSecurity`.
If the URL does not start with `/api`, Spring Security defaults to `webHttpSecurity`, which has an implied `securityMatcher` that matches any request. If the URL does not start with `/api`, Spring Security defaults to `webHttpSecurity`, which has an implied `securityMatcher` that matches any request.
[[modular-serverhttpsecurity-configuration]]
== Modular ServerHttpSecurity Configuration
Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it within the `SecurityWebFilterChain` Bean declaration.
However, there are times that users may want to modularize the configuration.
This can be done using:
* xref:#serverhttpsecurity-customizer-bean[Customizer<ServerHttpSecurity> Beans]
* xref:#top-level-customizer-bean[Top Level ServerHttpSecurity Customizer Beans]
// FIXME: this needs to link to appropriate spot
// NOTE: If you are using Spring Security's xref:servlet/configuration/kotlin.adoc[], then you can also expose `*Dsl -> Unit` Beans as outlined in xref:./kotlin.adoc#modular-httpsecuritydsl-configuration[Modular HttpSecurityDsl Configuration].
[[serverhttpsecurity-customizer-bean]]
=== Customizer<ServerHttpSecurity> Beans
If you would like to modularize your security configuration you can place logic in a `Customizer<ServerHttpSecurity>` Bean.
For example, the following configuration will ensure all `ServerHttpSecurity` instances are configured to:
include-code::./ServerHttpSecurityCustomizerBeanConfiguration[tag=httpSecurityCustomizer,indent=0]
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
<2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
[[top-level-customizer-bean]]
=== Top Level ServerHttpSecurity Customizer Beans
If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level `HttpSecurity` `Customizer` Beans.
A top level `HttpSecurity` `Customizer` type can be summarized as any `Customizer<T>` that matches `public HttpSecurity.*(Customizer<T>)`.
This translates to any `Customizer<T>` that is a single argument to a public method on javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity[].
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 javadoc:org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#contentTypeOptions(org.springframework.security.config.Customizer)[] 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 javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity#headers(org.springframework.security.config.Customizer)[].
For example, the following configuration will ensure that the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] is set to `object-src 'none'`:
include-code::./TopLevelCustomizerBeanConfiguration[tag=headersCustomizer,indent=0]
[[customizer-bean-ordering]]
=== Customizer Bean Ordering
First each xref:#httpsecurity-customizer-bean[Customizer<HttpSecurity> Bean] is applied using https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html#orderedStream()[ObjectProvider#orderedStream()].
This means that if there are multiple `Customizer<HttpSecurity>` Beans, the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[@Order] annotation can be added to the Bean definitions to control the ordering.
Next every xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans] 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:
include-code::./CustomizerBeanOrderingConfiguration[tag=sample,indent=0]
<1> First 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.
<2> The `userAuthorization` is applied next due to being an instance of `Customizer<HttpSecurity>`
<3> The order that the `Customizer` types are undefined.
In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` 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.
<4> After all of the `Customizer` Beans are applied, the `HttpSecurity` is passed in as a Bean.

View File

@ -664,6 +664,75 @@ class Config {
---- ----
====== ======
[[modular-httpsecurity-configuration]]
== Modular HttpSecurity Configuration
Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it in a single `SecurityFilterChain` instance.
However, there are times that users may want to modularize the configuration.
This can be done using:
* xref:#httpsecurity-customizer-bean[Customizer<HttpSecurity> Beans]
* xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans]
NOTE: If you are using Spring Security's xref:servlet/configuration/kotlin.adoc[], then you can also expose `*Dsl -> Unit` Beans as outlined in xref:./kotlin.adoc#modular-httpsecuritydsl-configuration[Modular HttpSecurityDsl Configuration].
[[httpsecurity-customizer-bean]]
=== Customizer<HttpSecurity> Beans
If you would like to modularize your security configuration you can place logic in a `Customizer<HttpSecurity>` Bean.
For example, the following configuration will ensure all `HttpSecurity` instances are configured to:
include-code::./HttpSecurityCustomizerBeanConfiguration[tag=httpSecurityCustomizer,indent=0]
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
<2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
[[top-level-customizer-bean]]
=== Top Level HttpSecurity Customizer Beans
If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level `HttpSecurity` `Customizer` Beans.
A top level `HttpSecurity` `Customizer` type can be summarized as any `Customizer<T>` that matches `public HttpSecurity.*(Customizer<T>)`.
This translates to any `Customizer<T>` that is a single argument to a public method on javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity[].
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 javadoc:org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#contentTypeOptions(org.springframework.security.config.Customizer)[] 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 javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity#headers(org.springframework.security.config.Customizer)[].
For example, the following configuration will ensure that the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] is set to `object-src 'none'`:
include-code::./TopLevelCustomizerBeanConfiguration[tag=headersCustomizer,indent=0]
[[customizer-bean-ordering]]
=== Customizer Bean Ordering
First each xref:#httpsecurity-customizer-bean[Customizer<HttpSecurity> Bean] is applied using https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html#orderedStream()[ObjectProvider#orderedStream()].
This means that if there are multiple `Customizer<HttpSecurity>` Beans, the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[@Order] annotation can be added to the Bean definitions to control the ordering.
Next every xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans] 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:
include-code::./CustomizerBeanOrderingConfiguration[tag=sample,indent=0]
<1> First 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.
<2> The `userAuthorization` is applied next due to being an instance of `Customizer<HttpSecurity>`
<3> The order that the `Customizer` types are undefined.
In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` 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.
<4> After all of the `Customizer` Beans are applied, the `HttpSecurity` is passed in as a Bean.
[[post-processing-configured-objects]] [[post-processing-configured-objects]]
== Post Processing Configured Objects == Post Processing Configured Objects

View File

@ -346,3 +346,76 @@ class BankingSecurityConfig {
This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last). This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last).
Requests that match `/`, `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication. Requests that match `/`, `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains. Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.
[[modular-httpsecuritydsl-configuration]]
== Modular HttpSecurityDsl Configuration
Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it in a single `SecurityFilterChain` instance.
However, there are times that users may want to modularize the configuration.
This can be done using:
* xref:#httpsecuritydsl-bean[HttpSecurityDsl.() -> Unit Beans]
* xref:#top-level-dsl-bean[Top Level Security Dsl Beans]
NOTE: Since the Spring Security Kotlin Dsl (`HttpSecurityDsl`) uses `HttpSecurity`, all of the Java xref:./kotlin.adoc#modular-bean-configuration[Modular Bean Customization] is applied before xref:#modular-httpsecuritydsl-configuration[Modular HttpSecurity Configuration].
[[httpsecuritydsl-bean]]
=== HttpSecurityDsl.() -> Unit Beans
If you would like to modularize your security configuration you can place logic in a `HttpSecurityDsl.() -> Unit` Bean.
For example, the following configuration will ensure all `HttpSecurityDsl` instances are configured to:
include-code::./HttpSecurityDslBeanConfiguration[tag=httpSecurityDslBean,indent=0]
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
<2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
[[top-level-dsl-bean]]
=== Top Level Security Dsl Beans
If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level Security Dsl Beans.
A top level Security Dsl can be summarized as any class Dsl class that matches `public HttpSecurityDsl.*(<Dsl>)`.
This translates to any Security Dsl that is a single argument to a public method on `HttpSecurityDsl`.
A few examples can help to clarify.
If `ContentTypeOptionsDsl.() -> Unit` is published as a Bean, it will not be be automatically applied because it is an argument to `HeadersDsl#contentTypeOptions(ContentTypeOptionsDsl.() -> Unit)` and is not an argument to a method defined on `HttpSecurityDsl`.
However, if `HeadersDsl.() -> Unit` is published as a Bean, it will be automatically applied because it is an argument to `HttpSecurityDsl.headers(HeadersDsl.() -> Unit)`.
For example, the following configuration ensure all `HttpSecurityDsl` instances are configured to:
include-code::./TopLevelDslBeanConfiguration[tag=headersSecurity,indent=0]
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
[[dsl-bean-ordering]]
=== Dsl Bean Ordering
First, all xref:servlet/configuration/java.adoc#modular-httpsecurity-configuration[Modular HttpSecurity Configuration] is applied since the Kotlin Dsl uses an `HttpSecurity` Bean.
Second, each xref:#httpsecuritydsl-bean[HttpSecurityDsl.() -> Unit Beans] is applied using https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html#orderedStream()[ObjectProvider#orderedStream()].
This means that if there are multiple `HttpSecurity.() -> Unit` Beans, the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[@Order] annotation can be added to the Bean definitions to control the ordering.
Next, every xref:#top-level-dsl-bean[Top Level Security Dsl Beans] type is looked up and each is is applied using `ObjectProvider#orderedStream()`.
If there is are differt types of top level security Beans (.e.g. `HeadersDsl.() -> Unit` and `HttpsRedirectDsl.() -> Unit`), then the order that each Dsl type is invoked is undefined.
However, the order that each instance of of the same top level security Bean type is defined by `ObjectProvider#orderedStream()` and can be controlled using `@Order` on the Bean the definitions.
Finally, the `HttpSecurityDsl` Bean is injected as a Bean.
All `*Dsl.() -> Unit` Beans are applied before the `HttpSecurityDsl` Bean is created.
This allows overriding the customizations provided by the `*Dsl.() -> Unit` Beans.
You can find an example below that illustrates the ordering:
include-code::./DslBeanOrderingConfiguration[tag=sample,indent=0]
<1> All xref:servlet/configuration/java.adoc#modular-httpsecurity-configuration[Modular HttpSecurity Configuration] is applied since the Kotlin Dsl uses an `HttpSecurity` Bean.
<2> All `HttpSecurity.() -> Unit` instances are applied.
The `adminAuthorization` Bean has the highest `@Order` so it is applied first.
If there are no `@Order` annotations on the `HttpSecurity.() -> Unit` Beans or the `@Order` annotations had the same value, then the order that the `HttpSecurity.() -> Unit` instances are applied is undefined.
<3> The `userAuthorization` is applied next due to being an instance of `HttpSecurity.() -> Unit`
<4> The order that the `*Dsl.() -> Unit` types are undefined.
In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` 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 `HeadersDsl.() -> Unit` Beans.
<5> After all of the `*Dsl.() -> Unit` Beans are applied, the `HttpSecurityDsl` is passed in as a Bean.

View File

@ -15,6 +15,7 @@ Each section that follows will indicate the more notable removals as well as the
== Config == Config
* Support modular modular configuration in xref::servlet/configuration/java.adoc#modular-httpsecurity-configuration[Servlets] and xref::reactive/configuration/webflux.adoc#modular-serverhttpsecurity-configuration[WebFlux]
* Removed `and()` from the `HttpSecurity` DSL in favor of using the lambda methods * Removed `and()` from the `HttpSecurity` DSL in favor of using the lambda methods
* Removed `authorizeRequests` in favor of `authorizeHttpRequests` * Removed `authorizeRequests` in favor of `authorizeHttpRequests`
* Simplified expression migration for `authorizeRequests` * Simplified expression migration for `authorizeRequests`

View File

@ -0,0 +1,99 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.customizerbeanordering;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.reactive.config.EnableWebFlux;
/**
*
*/
@EnableWebFlux
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
class CustomizerBeanOrderingConfiguration {
// tag::sample[]
@Bean // <4>
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchange) -> exchange
.anyExchange().authenticated()
);
return http.build();
// @formatter:on
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) // <2>
Customizer<ServerHttpSecurity> userAuthorization() {
// @formatter:off
return (http) -> http
.authorizeExchange((exchange) -> exchange
.pathMatchers("/users/**").hasRole("USER")
);
// @formatter:on
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) // <1>
Customizer<ServerHttpSecurity> adminAuthorization() {
// @formatter:off
return (http) -> http
.authorizeExchange((exchange) -> exchange
.pathMatchers("/admins/**").hasRole("ADMIN")
);
// @formatter:on
}
// <3>
@Bean
Customizer<ServerHttpSecurity.HeaderSpec> contentSecurityPolicy() {
// @formatter:off
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
.policyDirectives("object-src 'none'")
);
// @formatter:on
}
@Bean
Customizer<ServerHttpSecurity.HeaderSpec> contentTypeOptions() {
// @formatter:off
return (headers) -> headers
.contentTypeOptions(Customizer.withDefaults());
// @formatter:on
}
@Bean
Customizer<ServerHttpSecurity.HttpsRedirectSpec> httpsRedirect() {
// @formatter:off
return Customizer.withDefaults();
// @formatter:on
}
// end::sample[]
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.customizerbeanordering;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class CustomizerBeanOrderingTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private WebTestClient webTest;
@Test
void authorizationOrdered() throws Exception {
this.spring.register(
CustomizerBeanOrderingConfiguration.class).autowire();
// @formatter:off
this.webTest.mutateWith(mockUser("admin").roles("ADMIN"))
.get()
.uri("https://localhost/admins/1")
.exchange()
.expectStatus().isOk();
this.webTest.mutateWith(mockUser("user").roles("USER"))
.get()
.uri("https://localhost/admins/1")
.exchange()
.expectStatus().isForbidden();
this.webTest.mutateWith(mockUser("user").roles("USER"))
.get()
.uri("https://localhost/users/1")
.exchange()
.expectStatus().isOk();
this.webTest.mutateWith(mockUser("user").roles("OTHER"))
.get()
.uri("https://localhost/users/1")
.exchange()
.expectStatus().isForbidden();
this.webTest.mutateWith(mockUser("authenticated").roles("OTHER"))
.get()
.uri("https://localhost/other")
.exchange()
.expectStatus().isOk();
// @formatter:on
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.serverhttpsecuritycustomizerbean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
*
*/
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
class ServerHttpSecurityCustomizerBeanConfiguration {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchange) -> exchange
.anyExchange().authenticated()
);
return http.build();
// @formatter:on
}
// tag::httpSecurityCustomizer[]
@Bean
Customizer<ServerHttpSecurity> httpSecurityCustomizer() {
// @formatter:off
return (http) -> http
.headers((headers) -> headers
.contentSecurityPolicy((csp) -> csp
// <1>
.policyDirectives("object-src 'none'")
)
)
// <2>
.redirectToHttps(Customizer.withDefaults());
// @formatter:on
}
// end::httpSecurityCustomizer[]
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.serverhttpsecuritycustomizerbean;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class ServerHttpSecurityCustomizerBeanTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private WebTestClient webTest;
@Test
void httpSecurityCustomizer() throws Exception {
this.spring.register(
ServerHttpSecurityCustomizerBeanConfiguration.class).autowire();
// @formatter:off
this.webTest
.get()
.uri("http://localhost/")
.exchange()
.expectHeader().location("https://localhost/")
.expectHeader()
.value("Content-Security-Policy", csp ->
assertThat(csp).isEqualTo("object-src 'none'")
);
// @formatter:on
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.toplevelcustomizerbean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.reactive.config.EnableWebFlux;
/**
*
*/
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
public class TopLevelCustomizerBeanConfiguration {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchange) -> exchange
.anyExchange().authenticated()
);
return http.build();
// @formatter:on
}
// tag::headersCustomizer[]
@Bean
Customizer<ServerHttpSecurity.HeaderSpec> headersSecurity() {
// @formatter:off
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
// <1>
.policyDirectives("object-src 'none'")
);
// @formatter:on
}
// end::headersCustomizer[]
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.toplevelcustomizerbean;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class TopLevelCustomizerBeanTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private WebTestClient webTest;
@Test
void headersCustomizer() throws Exception {
this.spring.register(TopLevelCustomizerBeanConfiguration.class).autowire();
// @formatter:off
this.webTest
.get()
.uri("http://localhost/")
.exchange()
.expectHeader()
.value("Content-Security-Policy", csp ->
assertThat(csp).isEqualTo("object-src 'none'")
);
// @formatter:on
}
private static @NotNull ResultMatcher cspIsObjectSrcNone() {
return header().string("Content-Security-Policy", "object-src 'none'");
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.customizerbeanordering;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ThrowingCustomizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.annotation.web.configurers.HttpsRedirectConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
*
*/
@EnableWebMvc
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class CustomizerBeanOrderingConfiguration {
// tag::sample[]
@Bean // <4>
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
);
return http.build();
// @formatter:on
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) // <2>
ThrowingCustomizer<HttpSecurity> userAuthorization() {
// @formatter:off
return (http) -> http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/users/**").hasRole("USER")
);
// @formatter:on
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) // <1>
ThrowingCustomizer<HttpSecurity> adminAuthorization() {
// @formatter:off
return (http) -> http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/admins/**").hasRole("ADMIN")
);
// @formatter:on
}
// <3>
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> contentSecurityPolicy() {
// @formatter:off
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
.policyDirectives("object-src 'none'")
);
// @formatter:on
}
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> contentTypeOptions() {
// @formatter:off
return (headers) -> headers
.contentTypeOptions(Customizer.withDefaults());
// @formatter:on
}
@Bean
Customizer<HttpsRedirectConfigurer<HttpSecurity>> httpsRedirect() {
// @formatter:off
return Customizer.withDefaults();
// @formatter:on
}
// end::sample[]
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.customizerbeanordering;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class CustomizerBeanOrderingTests {
public final SpringTestContext spring = new SpringTestContext(this).mockMvcAfterSpringSecurityOk();
@Autowired
private MockMvc mockMvc;
@Test
void authorizationOrdered() throws Exception {
this.spring.register(
CustomizerBeanOrderingConfiguration.class).autowire();
// @formatter:off
this.mockMvc
.perform(get("https://localhost/admins/1").with(user("admin").roles("ADMIN")))
.andExpect(status().isOk());
this.mockMvc
.perform(get("https://localhost/admins/1").with(user("user").roles("USER")))
.andExpect(status().isForbidden());
this.mockMvc
.perform(get("https://localhost/users/1").with(user("user").roles("USER")))
.andExpect(status().isOk());
this.mockMvc
.perform(get("https://localhost/users/1").with(user("user").roles("OTHER")))
.andExpect(status().isForbidden());
this.mockMvc
.perform(get("https://localhost/other").with(user("authenticated").roles("OTHER")))
.andExpect(status().isOk());
// @formatter:on
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.httpsecuritycustomizerbean;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ThrowingCustomizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
*
*/
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class HttpSecurityCustomizerBeanConfiguration {
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
);
return http.build();
// @formatter:on
}
// tag::httpSecurityCustomizer[]
@Bean
ThrowingCustomizer<HttpSecurity> httpSecurityCustomizer() {
// @formatter:off
return (http) -> http
.headers((headers) -> headers
.contentSecurityPolicy((csp) -> csp
// <1>
.policyDirectives("object-src 'none'")
)
)
// <2>
.redirectToHttps(Customizer.withDefaults());
// @formatter:on
}
// end::httpSecurityCustomizer[]
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.httpsecuritycustomizerbean;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ThrowingCustomizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class HttpSecurityCustomizerBeanTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private MockMvc mockMvc;
@Test
void httpSecurityCustomizer() throws Exception {
this.spring.register(HttpSecurityCustomizerBeanConfiguration.class).autowire();
// @formatter:off
this.mockMvc
.perform(get("/"))
.andExpect(redirectsToHttps());
// headers are not sent back as a part of the redirect to https, so a separate request is necessary
this.mockMvc.perform(get("https://localhost/"))
.andExpect(cspIsObjectSrcNone());
// @formatter:on
}
private static @NotNull ResultMatcher redirectsToHttps() {
return mvcResult -> assertThat(
mvcResult.getResponse().getRedirectedUrl()).startsWith("https://");
}
private static @NotNull ResultMatcher cspIsObjectSrcNone() {
return header().string("Content-Security-Policy", "object-src 'none'");
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.toplevelcustomizerbean;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ThrowingCustomizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
*
*/
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class TopLevelCustomizerBeanConfiguration {
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
);
return http.build();
// @formatter:on
}
// tag::headersCustomizer[]
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> headersSecurity() {
// @formatter:off
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
// <1>
.policyDirectives("object-src 'none'")
);
// @formatter:on
}
// end::headersCustomizer[]
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.toplevelcustomizerbean;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ThrowingCustomizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class TopLevelCustomizerBeanTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private MockMvc mockMvc;
@Test
void headersCustomizer() throws Exception {
this.spring.register(TopLevelCustomizerBeanConfiguration.class).autowire();
// @formatter:off
this.mockMvc
.perform(get("/"))
.andExpect(cspIsObjectSrcNone());
// @formatter:on
}
private static @NotNull ResultMatcher cspIsObjectSrcNone() {
return header().string("Content-Security-Policy", "object-src 'none'");
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.reactive.configuration.customizerbeanordering
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.security.config.Customizer
import org.springframework.security.config.ThrowingCustomizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.config.annotation.web.configurers.HttpsRedirectConfigurer
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.anyExchange
/**
*
*/
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
internal class CustomizerBeanOrderingConfiguration {
// tag::sample[]
@Bean // <4>
fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
// @formatter:off
http
.authorizeExchange({ exchanges -> exchanges
.anyExchange().authenticated()
})
return http.build()
// @formatter:on
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) // <2>
fun userAuthorization(): Customizer<ServerHttpSecurity> {
// @formatter:off
return Customizer { http -> http
.authorizeExchange { exchanges -> exchanges
.pathMatchers("/users/**").hasRole("USER")
}
}
// @formatter:on
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) // <1>
fun adminAuthorization(): Customizer<ServerHttpSecurity> {
// @formatter:off
return ThrowingCustomizer { http -> http
.authorizeExchange { exchanges -> exchanges
.pathMatchers("/admins/**").hasRole("ADMIN")
}
}
// @formatter:on
}
// <3>
@Bean
fun contentSecurityPolicy(): Customizer<ServerHttpSecurity.HeaderSpec> {
// @formatter:off
return Customizer { headers -> headers
.contentSecurityPolicy { csp -> csp
.policyDirectives("object-src 'none'")
}
}
// @formatter:on
}
@Bean
fun contentTypeOptions(): Customizer<ServerHttpSecurity.HeaderSpec> {
// @formatter:off
return Customizer { headers -> headers
.contentTypeOptions(Customizer.withDefaults())
}
// @formatter:on
}
@Bean
fun httpsRedirect(): Customizer<ServerHttpSecurity.HttpsRedirectSpec> {
// @formatter:off
return Customizer.withDefaults()
// @formatter:on
}
// end::sample[]
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.reactive.configuration.customizerbeanordering
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
/**
*
*/
@ExtendWith(SpringTestContextExtension::class)
class CustomizerBeanOrderingTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var webTest: WebTestClient
@Test
fun authorizationOrdered() {
this.spring.register(CustomizerBeanOrderingConfiguration::class.java).autowire()
// @formatter:off
this.webTest.mutateWith(mockUser("admin").roles("ADMIN"))
.get()
.uri("https://localhost/admins/1")
.exchange()
.expectStatus().isOk
this.webTest.mutateWith(mockUser("user").roles("USER"))
.get()
.uri("https://localhost/admins/1")
.exchange()
.expectStatus().isForbidden
this.webTest.mutateWith(mockUser("user").roles("USER"))
.get()
.uri("https://localhost/users/1")
.exchange()
.expectStatus().isOk
this.webTest.mutateWith(mockUser("user").roles("OTHER"))
.get()
.uri("https://localhost/users/1")
.exchange()
.expectStatus().isForbidden
this.webTest.mutateWith(mockUser("other").roles("OTHER"))
.get()
.uri("https://localhost/other")
.exchange()
.expectStatus().isOk
// @formatter:on
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.reactive.configuration.dslbeanordering
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.*
import org.springframework.security.web.server.SecurityWebFilterChain
/**
*
*/
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
internal class DslBeanOrderingConfiguration {
// tag::sample[]
// All of the Java Modular Configuration is applied first <1>
@Bean // <5>
fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
// @formatter:off
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
}
// @formatter:on
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) // <3>
fun userAuthorization(): ServerHttpSecurityDsl.() -> Unit {
// @formatter:off
return {
authorizeExchange {
authorize("/users/**", hasRole("USER"))
}
}
// @formatter:on
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) // <2>
fun adminAuthorization(): ServerHttpSecurityDsl.() -> Unit {
// @formatter:off
return {
authorizeExchange {
authorize("/admins/**", hasRole("ADMIN"))
}
}
// @formatter:on
}
// <4>
@Bean
fun contentSecurityPolicy(): ServerHeadersDsl.() -> Unit {
// @formatter:off
return {
contentSecurityPolicy {
policyDirectives = "object-src 'none'"
}
}
// @formatter:on
}
@Bean
fun contentTypeOptions(): ServerHeadersDsl.() -> Unit {
// @formatter:off
return {
contentTypeOptions { }
}
// @formatter:on
}
@Bean
fun httpsRedirect(): ServerHttpsRedirectDsl.() -> Unit {
// @formatter:off
return { }
// @formatter:on
}
// end::sample[]
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.reactive.configuration.dslbeanordering
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.kt.docs.servlet.configuration.customizerbeanordering.CustomizerBeanOrderingConfiguration
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
*
*/
@ExtendWith(SpringTestContextExtension::class)
class DslBeanOrderingTests {
@JvmField
val spring = SpringTestContext(this).mockMvcAfterSpringSecurityOk()
@Autowired
lateinit var webTest: WebTestClient
@Test
fun dslOrdered() {
this.spring.register(org.springframework.security.kt.docs.reactive.configuration.customizerbeanordering.CustomizerBeanOrderingConfiguration::class.java).autowire()
// @formatter:off
this.webTest.mutateWith(mockUser("admin").roles("ADMIN"))
.get()
.uri("https://localhost/admins/1")
.exchange()
.expectStatus().isOk
this.webTest.mutateWith(mockUser("user").roles("USER"))
.get()
.uri("https://localhost/admins/1")
.exchange()
.expectStatus().isForbidden
this.webTest.mutateWith(mockUser("user").roles("USER"))
.get()
.uri("https://localhost/users/1")
.exchange()
.expectStatus().isOk
this.webTest.mutateWith(mockUser("user").roles("OTHER"))
.get()
.uri("https://localhost/users/1")
.exchange()
.expectStatus().isForbidden
this.webTest.mutateWith(mockUser("other").roles("OTHER"))
.get()
.uri("https://localhost/other")
.exchange()
.expectStatus().isOk
// @formatter:on
}
}

View File

@ -0,0 +1,44 @@
package org.springframework.security.kt.docs.reactive.configuration.serverhttpsecuritycustomizerbean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.server.SecurityWebFilterChain
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
class ServerHttpSecurityCustomizerBeanConfiguration {
@Bean
fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
// @formatter:off
http
.authorizeExchange({ exchanges -> exchanges
.anyExchange().authenticated()
})
return http.build()
// @formatter:on
}
// tag::httpSecurityCustomizer[]
@Bean
fun httpSecurityCustomizer(): Customizer<ServerHttpSecurity> {
// @formatter:off
return Customizer { http -> http
.headers { headers -> headers
.contentSecurityPolicy { csp -> csp
// <1>
.policyDirectives("object-src 'none'")
}
}
// <2>
.redirectToHttps(Customizer.withDefaults())
}
// @formatter:on
}
// end::httpSecurityCustomizer[]
}

View File

@ -0,0 +1,37 @@
package org.springframework.security.kt.docs.reactive.configuration.serverhttpsecuritycustomizerbean
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.test.web.reactive.server.WebTestClient
import java.util.function.Consumer
@ExtendWith(SpringTestContextExtension::class)
class ServerHttpSecurityCustomizerBeanTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var webTest: WebTestClient
@Test
fun `serverhttpsecurity customizer config`() {
this.spring.register(ServerHttpSecurityCustomizerBeanConfiguration::class.java).autowire()
// @formatter:off
this.webTest
.get()
.uri("http://localhost/")
.exchange()
.expectHeader().location("https://localhost/")
.expectHeader()
.value("Content-Security-Policy", Consumer { csp ->
assertThat(csp).isEqualTo("object-src 'none'")
})
// @formatter:on
}
}

View File

@ -0,0 +1,42 @@
package org.springframework.security.kt.docs.reactive.configuration.serverhttpsecuritydslbean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.config.web.server.ServerHttpSecurityDsl
import org.springframework.security.config.web.server.invoke
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.web.servlet.config.annotation.EnableWebMvc
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
class ServerHttpSecurityDslBeanConfiguration {
@Bean
fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
}
}
// tag::httpSecurityDslBean[]
@Bean
fun httpSecurityDslBean(): ServerHttpSecurityDsl.() -> Unit {
return {
headers {
contentSecurityPolicy {
// <1>
policyDirectives = "object-src 'none'"
}
}
// <2>
redirectToHttps { }
}
}
// end::httpSecurityDslBean[]
}

View File

@ -0,0 +1,38 @@
package org.springframework.security.kt.docs.reactive.configuration.serverhttpsecuritydslbean
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import java.util.function.Consumer
@ExtendWith(SpringTestContextExtension::class)
class ServerHttpSecurityDslBeanTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var webTest: WebTestClient
@Test
fun `ServerHttpSecurityDslBean`() {
this.spring.register(ServerHttpSecurityDslBeanConfiguration::class.java).autowire()
// @formatter:off
this.webTest
.get()
.uri("http://localhost/")
.exchange()
.expectHeader().location("https://localhost/")
.expectHeader().value("Content-Security-Policy", Consumer { csp ->
assertThat(csp).isEqualTo("object-src 'none'")
})
// @formatter:on
}
}

View File

@ -0,0 +1,43 @@
package org.springframework.security.kt.docs.reactive.configuration.toplevelcustomizerbean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.server.SecurityWebFilterChain
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
class TopLevelCustomizerBeanConfiguration {
@Bean
fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
// @formatter:off
http
.authorizeExchange({ exchanges -> exchanges
.anyExchange().authenticated()
})
return http.build()
// @formatter:on
}
// tag::headersCustomizer[]
@Bean
fun headersSecurity(): Customizer<ServerHttpSecurity.HeaderSpec> {
// @formatter:off
return Customizer { headers -> headers
.contentSecurityPolicy { csp -> csp
// <1>
.policyDirectives("object-src 'none'")
}
}
// @formatter:on
}
// end::headersCustomizer[]
}

View File

@ -0,0 +1,35 @@
package org.springframework.security.kt.docs.reactive.configuration.toplevelcustomizerbean
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.test.web.reactive.server.WebTestClient
import java.util.function.Consumer
@ExtendWith(SpringTestContextExtension::class)
class TopLevelCustomizerBeanTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var webTest: WebTestClient
@Test
fun `top level dsl bean`() {
this.spring.register(TopLevelCustomizerBeanConfiguration::class.java).autowire()
// @formatter:off
this.webTest
.get()
.uri("http://localhost/")
.exchange()
.expectHeader().value("Content-Security-Policy", Consumer { csp ->
assertThat(csp).isEqualTo("object-src 'none'")
})
// @formatter:on
}
}

View File

@ -0,0 +1,36 @@
package org.springframework.security.kt.docs.reactive.configuration.topleveldslbean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHeadersDsl
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.config.web.server.invoke
import org.springframework.security.web.server.SecurityWebFilterChain
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
class TopLevelDslBeanConfiguration {
@Bean
fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
}
}
// tag::headersSecurity[]
@Bean
fun headersSecurity(): ServerHeadersDsl.() -> Unit {
return {
contentSecurityPolicy {
// <1>
policyDirectives = "object-src 'none'"
}
}
}
// end::headersSecurity[]
}

View File

@ -0,0 +1,37 @@
package org.springframework.security.kt.docs.reactive.configuration.topleveldslbean
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import java.util.function.Consumer
@ExtendWith(SpringTestContextExtension::class)
class TopLevelDslBeanTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var webTest: WebTestClient
@Test
fun `HttpSecurityDslBean`() {
this.spring.register(TopLevelDslBeanConfiguration::class.java).autowire()
// @formatter:off
this.webTest
.get()
.uri("http://localhost/")
.exchange()
.expectHeader().value("Content-Security-Policy", Consumer { csp ->
assertThat(csp).isEqualTo("object-src 'none'")
})
// @formatter:on
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.configuration.customizerbeanordering
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.security.config.Customizer
import org.springframework.security.config.ThrowingCustomizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.config.annotation.web.configurers.HttpsRedirectConfigurer
import org.springframework.security.web.SecurityFilterChain
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
*
*/
@EnableWebMvc
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
internal class CustomizerBeanOrderingConfiguration {
// tag::sample[]
@Bean // <4>
fun springSecurity(http: HttpSecurity): SecurityFilterChain {
// @formatter:off
http
.authorizeHttpRequests({ requests -> requests
.anyRequest().authenticated()
})
return http.build()
// @formatter:on
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) // <2>
fun userAuthorization(): ThrowingCustomizer<HttpSecurity> {
// @formatter:off
return ThrowingCustomizer { http -> http
.authorizeHttpRequests { requests -> requests
.requestMatchers("/users/**").hasRole("USER")
}
}
// @formatter:on
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) // <1>
fun adminAuthorization(): ThrowingCustomizer<HttpSecurity> {
// @formatter:off
return ThrowingCustomizer { http -> http
.authorizeHttpRequests { requests -> requests
.requestMatchers("/admins/**").hasRole("ADMIN")
}
}
// @formatter:on
}
// <3>
@Bean
fun contentSecurityPolicy(): Customizer<HeadersConfigurer<HttpSecurity>> {
// @formatter:off
return Customizer { headers -> headers
.contentSecurityPolicy { csp -> csp
.policyDirectives("object-src 'none'")
}
}
// @formatter:on
}
@Bean
fun contentTypeOptions(): Customizer<HeadersConfigurer<HttpSecurity>> {
// @formatter:off
return Customizer { headers -> headers
.contentTypeOptions(Customizer.withDefaults())
}
// @formatter:on
}
@Bean
fun httpsRedirect(): Customizer<HttpsRedirectConfigurer<HttpSecurity>> {
// @formatter:off
return Customizer.withDefaults<HttpsRedirectConfigurer<HttpSecurity>>()
// @formatter:on
}
// end::sample[]
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.configuration.customizerbeanordering
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
/**
*
*/
@ExtendWith(SpringTestContextExtension::class)
class CustomizerBeanOrderingTests {
@JvmField
val spring = SpringTestContext(this).mockMvcAfterSpringSecurityOk()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun authorizationOrdered() {
this.spring.register(CustomizerBeanOrderingConfiguration::class.java).autowire()
// @formatter:off
this.mockMvc.get("https://localhost/admins/1") {
with(user("admin").roles("ADMIN"))
}.andExpect {
status { isOk() }
}
this.mockMvc.get("https://localhost/admins/1") {
with(user("user").roles("USER"))
}.andExpect {
status { isForbidden() }
}
this.mockMvc.get("https://localhost/users/1") {
with(user("user").roles("USER"))
}.andExpect {
status { isOk() }
}
this.mockMvc.get("https://localhost/users/1") {
with(user("noUserRole").roles("OTHER"))
}.andExpect {
status { isForbidden() }
}
this.mockMvc.get("https://localhost/other") {
with(user("authenticated").roles("OTHER"))
}.andExpect {
status { isOk() }
}
// @formatter:on
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.configuration.dslbeanordering
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.security.config.Customizer
import org.springframework.security.config.ThrowingCustomizer
import org.springframework.security.config.annotation.web.HeadersDsl
import org.springframework.security.config.annotation.web.HttpSecurityDsl
import org.springframework.security.config.annotation.web.HttpsRedirectDsl
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.config.annotation.web.configurers.HttpsRedirectConfigurer
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.web.SecurityFilterChain
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
*
*/
@EnableWebMvc
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
internal class DslBeanOrderingConfiguration {
// tag::sample[]
// All of the Java Modular Configuration is applied first <1>
@Bean // <5>
fun springSecurity(http: HttpSecurity): SecurityFilterChain {
// @formatter:off
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
return http.build()
// @formatter:on
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) // <3>
fun userAuthorization(): HttpSecurityDsl.() -> Unit {
// @formatter:off
return {
authorizeHttpRequests {
authorize("/users/**", hasRole("USER"))
}
}
// @formatter:on
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) // <2>
fun adminAuthorization(): HttpSecurityDsl.() -> Unit {
// @formatter:off
return {
authorizeHttpRequests {
authorize("/admins/**", hasRole("ADMIN"))
}
}
// @formatter:on
}
// <4>
@Bean
fun contentSecurityPolicy(): HeadersDsl.() -> Unit {
// @formatter:off
return {
contentSecurityPolicy {
policyDirectives = "object-src 'none'"
}
}
// @formatter:on
}
@Bean
fun contentTypeOptions(): HeadersDsl.() -> Unit {
// @formatter:off
return {
contentTypeOptions { }
}
// @formatter:on
}
@Bean
fun httpsRedirect(): HttpsRedirectDsl.() -> Unit {
// @formatter:off
return { }
// @formatter:on
}
// end::sample[]
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.kt.docs.servlet.configuration.dslbeanordering
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.kt.docs.servlet.configuration.customizerbeanordering.CustomizerBeanOrderingConfiguration
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
*
*/
@ExtendWith(SpringTestContextExtension::class)
class DslBeanOrderingTests {
@JvmField
val spring = SpringTestContext(this).mockMvcAfterSpringSecurityOk()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun dslOrdered() {
this.spring.register(DslBeanOrderingConfiguration::class.java).autowire()
// @formatter:off
this.mockMvc.get("https://localhost/admins/1") {
with(user("admin").roles("ADMIN"))
}.andExpect {
status { isOk() }
}
this.mockMvc.get("https://localhost/admins/1") {
with(user("user").roles("USER"))
}.andExpect {
status { isForbidden() }
}
this.mockMvc.get("https://localhost/users/1") {
with(user("user").roles("USER"))
}.andExpect {
status { isOk() }
}
this.mockMvc.get("https://localhost/users/1") {
with(user("noUserRole").roles("OTHER"))
}.andExpect {
status { isForbidden() }
}
this.mockMvc.get("https://localhost/other") {
with(user("authenticated").roles("OTHER"))
}.andExpect {
status { isOk() }
}
// @formatter:on
}
}

View File

@ -0,0 +1,46 @@
package org.springframework.security.kt.docs.servlet.configuration.httpsecuritycustomizerbean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.Customizer
import org.springframework.security.config.ThrowingCustomizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.web.SecurityFilterChain
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class HttpSecurityCustomizerBeanConfiguration {
@Bean
fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
return http.build()
}
// tag::httpSecurityCustomizer[]
@Bean
fun httpSecurityCustomizer(): ThrowingCustomizer<HttpSecurity> {
// @formatter:off
return ThrowingCustomizer { http -> http
.headers { headers -> headers
.contentSecurityPolicy { csp -> csp
// <1>
.policyDirectives("object-src 'none'")
}
}
// <2>
.redirectToHttps(Customizer.withDefaults())
}
// @formatter:on
}
// end::httpSecurityCustomizer[]
}

View File

@ -0,0 +1,35 @@
package org.springframework.security.kt.docs.servlet.configuration.httpsecuritycustomizerbean
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
@ExtendWith(SpringTestContextExtension::class)
class HttpSecurityCustomizerBeanTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `httpsecurity customizer config`() {
this.spring.register(HttpSecurityCustomizerBeanConfiguration::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
redirectedUrl("https://localhost/")
}
this.mockMvc.get("https://localhost/")
.andExpect {
header {
string("Content-Security-Policy", "object-src 'none'")
}
}
}
}

View File

@ -0,0 +1,52 @@
package org.springframework.security.kt.docs.servlet.configuration.httpsecuritydslbean
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.HeadersDsl
import org.springframework.security.config.annotation.web.HttpSecurityDsl
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.web.SecurityFilterChain
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.web.servlet.config.annotation.EnableWebMvc
@EnableWebMvc
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class HttpSecurityDslBeanConfiguration {
@Bean
fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
return http.build()
}
// tag::httpSecurityDslBean[]
@Bean
fun httpSecurityDslBean(): HttpSecurityDsl.() -> Unit {
return {
headers {
contentSecurityPolicy {
// <1>
policyDirectives = "object-src 'none'"
}
}
// <2>
redirectToHttps { }
}
}
// end::httpSecurityDslBean[]
}

View File

@ -0,0 +1,36 @@
package org.springframework.security.kt.docs.servlet.configuration.httpsecuritydslbean
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
@ExtendWith(SpringTestContextExtension::class)
class HttpSecurityDslBeanTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `HttpSecurityDslBean`() {
this.spring.register(HttpSecurityDslBeanConfiguration::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
redirectedUrl("https://localhost/")
}
this.mockMvc.get("https://localhost/")
.andExpect {
header {
string("Content-Security-Policy", "object-src 'none'")
}
}
}
}

View File

@ -0,0 +1,42 @@
package org.springframework.security.kt.docs.servlet.configuration.toplevelcustomizerbean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.web.SecurityFilterChain
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class TopLevelCustomizerBeanConfiguration {
@Bean
fun springSecurity(http: HttpSecurity): SecurityFilterChain {
// @formatter:off
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
return http.build()
// @formatter:on
}
// tag::headersCustomizer[]
@Bean
fun headersSecurity(): Customizer<HeadersConfigurer<HttpSecurity>> {
// @formatter:off
return Customizer { headers -> headers
.contentSecurityPolicy { csp -> csp
// <1>
.policyDirectives("object-src 'none'")
}
}
// @formatter:on
}
// end::headersCustomizer[]
}

View File

@ -0,0 +1,31 @@
package org.springframework.security.kt.docs.servlet.configuration.toplevelcustomizerbean
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
@ExtendWith(SpringTestContextExtension::class)
class TopLevelCustomizerBeanTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `top level dsl bean`() {
this.spring.register(TopLevelCustomizerBeanConfiguration::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header {
string("Content-Security-Policy", "object-src 'none'")
}
}
}
}

View File

@ -0,0 +1,40 @@
package org.springframework.security.kt.docs.servlet.configuration.topleveldslbean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.HeadersDsl
import org.springframework.security.config.annotation.web.HttpSecurityDsl
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.web.SecurityFilterChain
import org.springframework.web.servlet.config.annotation.EnableWebMvc
@EnableWebMvc
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class TopLevelDslBeanConfiguration {
@Bean
fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
return http.build()
}
// tag::headersSecurity[]
@Bean
fun headersSecurity(): HeadersDsl.() -> Unit {
return {
contentSecurityPolicy {
// <1>
policyDirectives = "object-src 'none'"
}
}
}
// end::headersSecurity[]
}

View File

@ -0,0 +1,31 @@
package org.springframework.security.kt.docs.servlet.configuration.topleveldslbean
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
@ExtendWith(SpringTestContextExtension::class)
class TopLevelDslBeanTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `HttpSecurityDslBean`() {
this.spring.register(TopLevelDslBeanConfiguration::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header {
string("Content-Security-Policy", "object-src 'none'")
}
}
}
}