mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-10-22 18:28:51 +00:00
Add Modular Spring Security Configuration
Closes gh-16258
This commit is contained in:
parent
5c5efc9092
commit
a8f045eb50
@ -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;
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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]
|
||||
*
|
||||
|
@ -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].
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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`
|
||||
|
@ -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[]
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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'");
|
||||
}
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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'");
|
||||
}
|
||||
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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'");
|
||||
}
|
||||
}
|
@ -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[]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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[]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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[]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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[]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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[]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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[]
|
||||
|
||||
}
|
@ -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'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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[]
|
||||
}
|
@ -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'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user