Skip to content

Commit a8f045e

Browse files
committed
Add Modular Spring Security Configuration
Closes gh-16258
1 parent 5c5efc9 commit a8f045e

File tree

49 files changed

+3207
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3207
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2004-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config;
18+
19+
/**
20+
* A {@link Customizer} that allows invocation of code that throws a checked exception.
21+
*
22+
* @param <T> The type of input.
23+
*/
24+
@FunctionalInterface
25+
public interface ThrowingCustomizer<T> extends Customizer<T> {
26+
27+
/**
28+
* Default {@link Customizer#customize(Object)} that wraps any thrown checked
29+
* exceptions (by default in a {@link RuntimeException}).
30+
* @param t the object to customize
31+
*/
32+
default void customize(T t) {
33+
try {
34+
customizeWithException(t);
35+
}
36+
catch (RuntimeException ex) {
37+
throw ex;
38+
}
39+
catch (Exception ex) {
40+
throw new RuntimeException(ex);
41+
}
42+
}
43+
44+
/**
45+
* Performs the customization on the given object, possibly throwing a checked
46+
* exception.
47+
* @param t the object to customize
48+
* @throws Exception on error
49+
*/
50+
void customizeWithException(T t) throws Exception;
51+
52+
}

config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,24 @@
1616

1717
package org.springframework.security.config.annotation.web.configuration;
1818

19+
import java.lang.reflect.Modifier;
1920
import java.util.HashMap;
2021
import java.util.List;
2122
import java.util.Map;
2223

24+
import org.springframework.beans.factory.ObjectProvider;
2325
import org.springframework.beans.factory.annotation.Autowired;
2426
import org.springframework.context.ApplicationContext;
2527
import org.springframework.context.annotation.Bean;
2628
import org.springframework.context.annotation.Configuration;
2729
import org.springframework.context.annotation.Scope;
30+
import org.springframework.core.MethodParameter;
31+
import org.springframework.core.ResolvableType;
2832
import org.springframework.core.io.support.SpringFactoriesLoader;
2933
import org.springframework.security.authentication.AuthenticationEventPublisher;
3034
import org.springframework.security.authentication.AuthenticationManager;
3135
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
36+
import org.springframework.security.config.Customizer;
3237
import org.springframework.security.config.ObjectPostProcessor;
3338
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
3439
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@@ -46,6 +51,7 @@
4651
import org.springframework.security.crypto.password.PasswordEncoder;
4752
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
4853
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
54+
import org.springframework.util.ReflectionUtils;
4955
import org.springframework.util.function.ThrowingSupplier;
5056
import org.springframework.web.accept.ContentNegotiationStrategy;
5157
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
@@ -131,6 +137,8 @@ HttpSecurity httpSecurity() throws Exception {
131137
// @formatter:on
132138
applyCorsIfAvailable(http);
133139
applyDefaultConfigurers(http);
140+
applyHttpSecurityCustomizers(this.context, http);
141+
applyTopLevelCustomizers(this.context, http);
134142
return http;
135143
}
136144

@@ -160,6 +168,73 @@ private void applyDefaultConfigurers(HttpSecurity http) throws Exception {
160168
}
161169
}
162170

171+
/**
172+
* Applies all {@code Customizer<HttpSecurity>} Bean instances to the
173+
* {@link HttpSecurity} instance.
174+
* @param applicationContext the {@link ApplicationContext} to lookup Bean instances
175+
* @param http the {@link HttpSecurity} to apply the Beans to.
176+
*/
177+
private void applyHttpSecurityCustomizers(ApplicationContext applicationContext, HttpSecurity http) {
178+
ResolvableType httpSecurityCustomizerType = ResolvableType.forClassWithGenerics(Customizer.class,
179+
HttpSecurity.class);
180+
ObjectProvider<Customizer<HttpSecurity>> customizerProvider = this.context
181+
.getBeanProvider(httpSecurityCustomizerType);
182+
183+
// @formatter:off
184+
customizerProvider.orderedStream().forEach((customizer) ->
185+
customizer.customize(http)
186+
);
187+
// @formatter:on
188+
}
189+
190+
/**
191+
* Applies all {@link Customizer} Beans to {@link HttpSecurity}. For each public,
192+
* non-static method in HttpSecurity that accepts a Customizer
193+
* <ul>
194+
* <li>Use the {@link MethodParameter} (this preserves generics) to resolve all Beans
195+
* for that type</li>
196+
* <li>For each {@link Customizer} Bean invoke the {@link java.lang.reflect.Method}
197+
* with the {@link Customizer} Bean as the argument</li>
198+
* </ul>
199+
* @param context the {@link ApplicationContext}
200+
* @param http the {@link HttpSecurity}
201+
* @throws Exception
202+
*/
203+
private void applyTopLevelCustomizers(ApplicationContext context, HttpSecurity http) {
204+
ReflectionUtils.MethodFilter isCustomizerMethod = (method) -> {
205+
if (Modifier.isStatic(method.getModifiers())) {
206+
return false;
207+
}
208+
if (!Modifier.isPublic(method.getModifiers())) {
209+
return false;
210+
}
211+
if (!method.canAccess(http)) {
212+
return false;
213+
}
214+
if (method.getParameterCount() != 1) {
215+
return false;
216+
}
217+
if (method.getParameterTypes()[0] == Customizer.class) {
218+
return true;
219+
}
220+
return false;
221+
};
222+
ReflectionUtils.MethodCallback invokeWithEachCustomizerBean = (customizerMethod) -> {
223+
224+
MethodParameter customizerParameter = new MethodParameter(customizerMethod, 0);
225+
ResolvableType customizerType = ResolvableType.forMethodParameter(customizerParameter);
226+
ObjectProvider<?> customizerProvider = context.getBeanProvider(customizerType);
227+
228+
// @formatter:off
229+
customizerProvider.orderedStream().forEach((customizer) ->
230+
ReflectionUtils.invokeMethod(customizerMethod, http, customizer)
231+
);
232+
// @formatter:on
233+
234+
};
235+
ReflectionUtils.doWithMethods(HttpSecurity.class, invokeWithEachCustomizerBean, isCustomizerMethod);
236+
}
237+
163238
private Map<Class<?>, Object> createSharedObjects() {
164239
Map<Class<?>, Object> sharedObjects = new HashMap<>();
165240
sharedObjects.put(ApplicationContext.class, this.context);

config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.config.annotation.web.reactive;
1818

19+
import java.lang.reflect.Modifier;
1920
import java.util.Map;
2021

2122
import org.springframework.beans.BeansException;
@@ -28,10 +29,13 @@
2829
import org.springframework.context.annotation.Configuration;
2930
import org.springframework.context.annotation.Scope;
3031
import org.springframework.context.expression.BeanFactoryResolver;
32+
import org.springframework.core.MethodParameter;
3133
import org.springframework.core.ReactiveAdapterRegistry;
34+
import org.springframework.core.ResolvableType;
3235
import org.springframework.security.authentication.ReactiveAuthenticationManager;
3336
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
3437
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
38+
import org.springframework.security.config.Customizer;
3539
import org.springframework.security.config.ObjectPostProcessor;
3640
import org.springframework.security.config.web.server.ServerHttpSecurity;
3741
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
@@ -40,6 +44,7 @@
4044
import org.springframework.security.crypto.password.PasswordEncoder;
4145
import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
4246
import org.springframework.security.web.reactive.result.method.annotation.CurrentSecurityContextArgumentResolver;
47+
import org.springframework.util.ReflectionUtils;
4348
import org.springframework.web.reactive.config.WebFluxConfigurer;
4449
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
4550

@@ -154,6 +159,83 @@ CurrentSecurityContextArgumentResolver reactiveCurrentSecurityContextArgumentRes
154159

155160
@Bean(HTTPSECURITY_BEAN_NAME)
156161
@Scope("prototype")
162+
ServerHttpSecurity httpSecurity(ApplicationContext context) {
163+
ServerHttpSecurity http = httpSecurity();
164+
applyServerHttpSecurityCustomizers(context, http);
165+
applyTopLevelBeanCustomizers(context, http);
166+
return http;
167+
}
168+
169+
/**
170+
* Applies all {@code Custmizer<ServerHttpSecurity>} Beans to
171+
* {@link ServerHttpSecurity}.
172+
* @param context the {@link ApplicationContext}
173+
* @param http the {@link ServerHttpSecurity}
174+
* @throws Exception
175+
*/
176+
private void applyServerHttpSecurityCustomizers(ApplicationContext context, ServerHttpSecurity http) {
177+
ResolvableType httpSecurityCustomizerType = ResolvableType.forClassWithGenerics(Customizer.class,
178+
ServerHttpSecurity.class);
179+
ObjectProvider<Customizer<ServerHttpSecurity>> customizerProvider = context
180+
.getBeanProvider(httpSecurityCustomizerType);
181+
182+
// @formatter:off
183+
customizerProvider.orderedStream().forEach((customizer) ->
184+
customizer.customize(http)
185+
);
186+
// @formatter:on
187+
}
188+
189+
/**
190+
* Applies all {@link Customizer} Beans to top level {@link ServerHttpSecurity}
191+
* method.
192+
*
193+
* For each public, non-static method in ServerHttpSecurity that accepts a Customizer
194+
* <ul>
195+
* <li>Use the {@link MethodParameter} (this preserves generics) to resolve all Beans
196+
* for that type</li>
197+
* <li>For each {@link Customizer} Bean invoke the {@link java.lang.reflect.Method}
198+
* with the {@link Customizer} Bean as the argument</li>
199+
* </ul>
200+
* @param context the {@link ApplicationContext}
201+
* @param http the {@link ServerHttpSecurity}
202+
* @throws Exception
203+
*/
204+
private void applyTopLevelBeanCustomizers(ApplicationContext context, ServerHttpSecurity http) {
205+
ReflectionUtils.MethodFilter isCustomizerMethod = (method) -> {
206+
if (Modifier.isStatic(method.getModifiers())) {
207+
return false;
208+
}
209+
if (!Modifier.isPublic(method.getModifiers())) {
210+
return false;
211+
}
212+
if (!method.canAccess(http)) {
213+
return false;
214+
}
215+
if (method.getParameterCount() != 1) {
216+
return false;
217+
}
218+
if (method.getParameterTypes()[0] == Customizer.class) {
219+
return true;
220+
}
221+
return false;
222+
};
223+
ReflectionUtils.MethodCallback invokeWithEachCustomizerBean = (customizerMethod) -> {
224+
225+
MethodParameter customizerParameter = new MethodParameter(customizerMethod, 0);
226+
ResolvableType customizerType = ResolvableType.forMethodParameter(customizerParameter);
227+
ObjectProvider<?> customizerProvider = context.getBeanProvider(customizerType);
228+
229+
// @formatter:off
230+
customizerProvider.orderedStream().forEach((customizer) ->
231+
ReflectionUtils.invokeMethod(customizerMethod, http, customizer)
232+
);
233+
// @formatter:on
234+
235+
};
236+
ReflectionUtils.doWithMethods(ServerHttpSecurity.class, invokeWithEachCustomizerBean, isCustomizerMethod);
237+
}
238+
157239
ServerHttpSecurity httpSecurity() {
158240
ContextAwareServerHttpSecurity http = new ContextAwareServerHttpSecurity();
159241
// @formatter:off

config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,6 +1316,10 @@ private <T> String[] getBeanNamesForTypeOrEmpty(Class<T> beanClass) {
13161316
return this.context.getBeanNamesForType(beanClass);
13171317
}
13181318

1319+
ApplicationContext getApplicationContext() {
1320+
return this.context;
1321+
}
1322+
13191323
protected void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
13201324
this.context = applicationContext;
13211325
}

0 commit comments

Comments
 (0)