Explorar el Código

Add Modular Spring Security Configuration

Closes gh-16258
Rob Winch hace 2 días
padre
commit
a8f045eb50
Se han modificado 49 ficheros con 3207 adiciones y 0 borrados
  1. 52 0
      config/src/main/java/org/springframework/security/config/ThrowingCustomizer.java
  2. 75 0
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java
  3. 82 0
      config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java
  4. 4 0
      config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java
  5. 119 0
      config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt
  6. 119 0
      config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDsl.kt
  7. 209 0
      config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java
  8. 107 0
      config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java
  9. 33 0
      config/src/test/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDslTests.kt
  10. 69 0
      docs/modules/ROOT/pages/reactive/configuration/webflux.adoc
  11. 69 0
      docs/modules/ROOT/pages/servlet/configuration/java.adoc
  12. 73 0
      docs/modules/ROOT/pages/servlet/configuration/kotlin.adoc
  13. 1 0
      docs/modules/ROOT/pages/whats-new.adoc
  14. 99 0
      docs/src/test/java/org/springframework/security/docs/reactive/configuration/customizerbeanordering/CustomizerBeanOrderingConfiguration.java
  15. 76 0
      docs/src/test/java/org/springframework/security/docs/reactive/configuration/customizerbeanordering/CustomizerBeanOrderingTests.java
  16. 61 0
      docs/src/test/java/org/springframework/security/docs/reactive/configuration/serverhttpsecuritycustomizerbean/ServerHttpSecurityCustomizerBeanConfiguration.java
  17. 56 0
      docs/src/test/java/org/springframework/security/docs/reactive/configuration/serverhttpsecuritycustomizerbean/ServerHttpSecurityCustomizerBeanTests.java
  18. 58 0
      docs/src/test/java/org/springframework/security/docs/reactive/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanConfiguration.java
  19. 61 0
      docs/src/test/java/org/springframework/security/docs/reactive/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanTests.java
  20. 102 0
      docs/src/test/java/org/springframework/security/docs/servlet/configuration/customizerbeanordering/CustomizerBeanOrderingConfiguration.java
  21. 64 0
      docs/src/test/java/org/springframework/security/docs/servlet/configuration/customizerbeanordering/CustomizerBeanOrderingTests.java
  22. 77 0
      docs/src/test/java/org/springframework/security/docs/servlet/configuration/httpsecuritycustomizerbean/HttpSecurityCustomizerBeanConfiguration.java
  23. 74 0
      docs/src/test/java/org/springframework/security/docs/servlet/configuration/httpsecuritycustomizerbean/HttpSecurityCustomizerBeanTests.java
  24. 71 0
      docs/src/test/java/org/springframework/security/docs/servlet/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanConfiguration.java
  25. 66 0
      docs/src/test/java/org/springframework/security/docs/servlet/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanTests.java
  26. 104 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/customizerbeanordering/CustomizerBeanOrderingConfiguration.kt
  27. 74 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/customizerbeanordering/CustomizerBeanOrderingTests.kt
  28. 99 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/dslbeanordering/DslBeanOrderingConfiguration.kt
  29. 72 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/dslbeanordering/DslBeanOrderingTests.kt
  30. 44 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/serverhttpsecuritycustomizerbean/ServerHttpSecurityCustomizerBeanConfiguration.kt
  31. 37 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/serverhttpsecuritycustomizerbean/ServerHttpSecurityCustomizerBeanTests.kt
  32. 42 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/serverhttpsecuritydslbean/ServerHttpSecurityDslBeanConfiguration.kt
  33. 38 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/serverhttpsecuritydslbean/ServerHttpSecurityDslBeanTests.kt
  34. 43 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanConfiguration.kt
  35. 35 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanTests.kt
  36. 36 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/topleveldslbean/TopLevelDslBeanConfiguration.kt
  37. 37 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/topleveldslbean/TopLevelDslBeanTests.kt
  38. 104 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/customizerbeanordering/CustomizerBeanOrderingConfiguration.kt
  39. 72 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/customizerbeanordering/CustomizerBeanOrderingTests.kt
  40. 110 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/dslbeanordering/DslBeanOrderingConfiguration.kt
  41. 70 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/dslbeanordering/DslBeanOrderingTests.kt
  42. 46 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/httpsecuritycustomizerbean/HttpSecurityCustomizerBeanConfiguration.kt
  43. 35 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/httpsecuritycustomizerbean/HttpSecurityCustomizerBeanTests.kt
  44. 52 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/httpsecuritydslbean/HttpSecurityDslBeanConfiguration.kt
  45. 36 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/httpsecuritydslbean/HttpSecurityDslBeanTests.kt
  46. 42 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanConfiguration.kt
  47. 31 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanTests.kt
  48. 40 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/topleveldslbean/TopLevelDslBeanConfiguration.kt
  49. 31 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/topleveldslbean/TopLevelDslBeanTests.kt

+ 52 - 0
config/src/main/java/org/springframework/security/config/ThrowingCustomizer.java

@@ -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;
+
+}

+ 75 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java

@@ -16,19 +16,24 @@
 
 package org.springframework.security.config.annotation.web.configuration;
 
+import java.lang.reflect.Modifier;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 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.security.authentication.AuthenticationEventPublisher;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
+import org.springframework.security.config.Customizer;
 import org.springframework.security.config.ObjectPostProcessor;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 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.web.context.request.async.WebAsyncManagerIntegrationFilter;
 import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
+import org.springframework.util.ReflectionUtils;
 import org.springframework.util.function.ThrowingSupplier;
 import org.springframework.web.accept.ContentNegotiationStrategy;
 import org.springframework.web.accept.HeaderContentNegotiationStrategy;
@@ -131,6 +137,8 @@ class HttpSecurityConfiguration {
 		// @formatter:on
 		applyCorsIfAvailable(http);
 		applyDefaultConfigurers(http);
+		applyHttpSecurityCustomizers(this.context, http);
+		applyTopLevelCustomizers(this.context, 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() {
 		Map<Class<?>, Object> sharedObjects = new HashMap<>();
 		sharedObjects.put(ApplicationContext.class, this.context);

+ 82 - 0
config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java

@@ -16,6 +16,7 @@
 
 package org.springframework.security.config.annotation.web.reactive;
 
+import java.lang.reflect.Modifier;
 import java.util.Map;
 
 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.Scope;
 import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.core.MethodParameter;
 import org.springframework.core.ReactiveAdapterRegistry;
+import org.springframework.core.ResolvableType;
 import org.springframework.security.authentication.ReactiveAuthenticationManager;
 import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
 import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
+import org.springframework.security.config.Customizer;
 import org.springframework.security.config.ObjectPostProcessor;
 import org.springframework.security.config.web.server.ServerHttpSecurity;
 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.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
 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.result.method.annotation.ArgumentResolverConfigurer;
 
@@ -154,6 +159,83 @@ class ServerHttpSecurityConfiguration {
 
 	@Bean(HTTPSECURITY_BEAN_NAME)
 	@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() {
 		ContextAwareServerHttpSecurity http = new ContextAwareServerHttpSecurity();
 		// @formatter:off

+ 4 - 0
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

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

+ 119 - 0
config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt

@@ -18,14 +18,22 @@ package org.springframework.security.config.annotation.web
 
 import jakarta.servlet.Filter
 import jakarta.servlet.http.HttpServletRequest
+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.AuthenticationManager
+import org.springframework.security.config.Customizer
 import org.springframework.security.config.annotation.SecurityConfigurerAdapter
 import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
 import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
 import org.springframework.security.web.DefaultSecurityFilterChain
 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].
@@ -77,6 +85,117 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
     var authenticationManager: AuthenticationManager? = null
     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]
      *

+ 119 - 0
config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDsl.kt

@@ -16,13 +16,23 @@
 
 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.config.Customizer
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
 import org.springframework.security.web.server.SecurityWebFilterChain
 import org.springframework.security.web.server.context.ServerSecurityContextRepository
 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.WebFilter
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
 
 /**
  * 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 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
      * provided [ServerWebExchangeMatcher].

+ 209 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java

@@ -28,14 +28,20 @@ import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockedStatic;
+import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 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.context.event.EventListener;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
 import org.springframework.core.io.support.SpringFactoriesLoader;
 import org.springframework.mock.web.MockHttpSession;
 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.configurers.AbstractHttpConfigurer;
 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.test.SpringTestContext;
 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.UrlBasedCorsConfigurationSource;
 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.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.willAnswer;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 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.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
@@ -425,6 +435,77 @@ public class HttpSecurityConfigurationTests {
 			.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
 	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 {
 
 		@Override

+ 107 - 0
config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java

@@ -29,12 +29,17 @@ import io.micrometer.observation.ObservationRegistry;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
 import reactor.core.publisher.Mono;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 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.security.authentication.TestingAuthenticationToken;
 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.users.ReactiveAuthenticationTestConfiguration;
 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.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -267,6 +273,47 @@ public class ServerHttpSecurityConfigurationTests {
 		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
 	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);
+		}
+
+	}
+
 }

+ 33 - 0
config/src/test/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDslTests.kt

@@ -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'"
+                }
+            }
+        }
+    }
 
 }

+ 69 - 0
docs/modules/ROOT/pages/reactive/configuration/webflux.adoc

@@ -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`.
 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.

+ 69 - 0
docs/modules/ROOT/pages/servlet/configuration/java.adoc

@@ -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
 

+ 73 - 0
docs/modules/ROOT/pages/servlet/configuration/kotlin.adoc

@@ -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).
 	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.
+
+
+[[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.

+ 1 - 0
docs/modules/ROOT/pages/whats-new.adoc

@@ -15,6 +15,7 @@ Each section that follows will indicate the more notable removals as well as the
 
 == 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 `authorizeRequests` in favor of `authorizeHttpRequests`
 * Simplified expression migration for `authorizeRequests`

+ 99 - 0
docs/src/test/java/org/springframework/security/docs/reactive/configuration/customizerbeanordering/CustomizerBeanOrderingConfiguration.java

@@ -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[]
+
+}

+ 76 - 0
docs/src/test/java/org/springframework/security/docs/reactive/configuration/customizerbeanordering/CustomizerBeanOrderingTests.java

@@ -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
+	}
+
+}

+ 61 - 0
docs/src/test/java/org/springframework/security/docs/reactive/configuration/serverhttpsecuritycustomizerbean/ServerHttpSecurityCustomizerBeanConfiguration.java

@@ -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[]
+
+}

+ 56 - 0
docs/src/test/java/org/springframework/security/docs/reactive/configuration/serverhttpsecuritycustomizerbean/ServerHttpSecurityCustomizerBeanTests.java

@@ -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
+	}
+
+}

+ 58 - 0
docs/src/test/java/org/springframework/security/docs/reactive/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanConfiguration.java

@@ -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[]
+
+}

+ 61 - 0
docs/src/test/java/org/springframework/security/docs/reactive/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanTests.java

@@ -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'");
+	}
+}

+ 102 - 0
docs/src/test/java/org/springframework/security/docs/servlet/configuration/customizerbeanordering/CustomizerBeanOrderingConfiguration.java

@@ -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[]
+
+}

+ 64 - 0
docs/src/test/java/org/springframework/security/docs/servlet/configuration/customizerbeanordering/CustomizerBeanOrderingTests.java

@@ -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
+	}
+
+}

+ 77 - 0
docs/src/test/java/org/springframework/security/docs/servlet/configuration/httpsecuritycustomizerbean/HttpSecurityCustomizerBeanConfiguration.java

@@ -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[]
+
+}

+ 74 - 0
docs/src/test/java/org/springframework/security/docs/servlet/configuration/httpsecuritycustomizerbean/HttpSecurityCustomizerBeanTests.java

@@ -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'");
+	}
+
+}

+ 71 - 0
docs/src/test/java/org/springframework/security/docs/servlet/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanConfiguration.java

@@ -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[]
+
+}

+ 66 - 0
docs/src/test/java/org/springframework/security/docs/servlet/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanTests.java

@@ -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'");
+	}
+}

+ 104 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/customizerbeanordering/CustomizerBeanOrderingConfiguration.kt

@@ -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[]
+}

+ 74 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/customizerbeanordering/CustomizerBeanOrderingTests.kt

@@ -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
+    }
+}

+ 99 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/dslbeanordering/DslBeanOrderingConfiguration.kt

@@ -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[]
+}

+ 72 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/dslbeanordering/DslBeanOrderingTests.kt

@@ -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
+    }
+}

+ 44 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/serverhttpsecuritycustomizerbean/ServerHttpSecurityCustomizerBeanConfiguration.kt

@@ -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[]
+
+}

+ 37 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/serverhttpsecuritycustomizerbean/ServerHttpSecurityCustomizerBeanTests.kt

@@ -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
+    }
+
+}

+ 42 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/serverhttpsecuritydslbean/ServerHttpSecurityDslBeanConfiguration.kt

@@ -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[]
+
+}

+ 38 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/serverhttpsecuritydslbean/ServerHttpSecurityDslBeanTests.kt

@@ -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
+    }
+}

+ 43 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanConfiguration.kt

@@ -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[]
+
+}

+ 35 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanTests.kt

@@ -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
+    }
+
+}

+ 36 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/topleveldslbean/TopLevelDslBeanConfiguration.kt

@@ -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[]
+}

+ 37 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/configuration/topleveldslbean/TopLevelDslBeanTests.kt

@@ -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
+    }
+}

+ 104 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/customizerbeanordering/CustomizerBeanOrderingConfiguration.kt

@@ -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[]
+}

+ 72 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/customizerbeanordering/CustomizerBeanOrderingTests.kt

@@ -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
+    }
+}

+ 110 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/dslbeanordering/DslBeanOrderingConfiguration.kt

@@ -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[]
+}

+ 70 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/dslbeanordering/DslBeanOrderingTests.kt

@@ -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
+    }
+}

+ 46 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/httpsecuritycustomizerbean/HttpSecurityCustomizerBeanConfiguration.kt

@@ -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[]
+
+}

+ 35 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/httpsecuritycustomizerbean/HttpSecurityCustomizerBeanTests.kt

@@ -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'")
+                }
+            }
+    }
+
+}

+ 52 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/httpsecuritydslbean/HttpSecurityDslBeanConfiguration.kt

@@ -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[]
+
+}

+ 36 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/httpsecuritydslbean/HttpSecurityDslBeanTests.kt

@@ -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'")
+                }
+            }
+    }
+}

+ 42 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanConfiguration.kt

@@ -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[]
+
+}

+ 31 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/toplevelcustomizerbean/TopLevelCustomizerBeanTests.kt

@@ -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'")
+                }
+            }
+    }
+
+}

+ 40 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/topleveldslbean/TopLevelDslBeanConfiguration.kt

@@ -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[]
+}

+ 31 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/configuration/topleveldslbean/TopLevelDslBeanTests.kt

@@ -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'")
+                }
+            }
+    }
+}