Skip to content

Commit 18427b6

Browse files
Configure WebInvocationPrivilegeEvaluator bean for multiple filter chains
Closes gh-10554
1 parent 7e17a00 commit 18427b6

File tree

9 files changed

+633
-14
lines changed

9 files changed

+633
-14
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java

+51-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121

2222
import javax.servlet.Filter;
23+
import javax.servlet.ServletContext;
2324
import javax.servlet.http.HttpServletRequest;
2425

2526
import org.apache.commons.logging.Log;
@@ -33,6 +34,7 @@
3334
import org.springframework.security.access.PermissionEvaluator;
3435
import org.springframework.security.access.expression.SecurityExpressionHandler;
3536
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
37+
import org.springframework.security.authorization.AuthorizationManager;
3638
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
3739
import org.springframework.security.config.annotation.ObjectPostProcessor;
3840
import org.springframework.security.config.annotation.SecurityBuilder;
@@ -47,17 +49,22 @@
4749
import org.springframework.security.web.FilterChainProxy;
4850
import org.springframework.security.web.FilterInvocation;
4951
import org.springframework.security.web.SecurityFilterChain;
52+
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
5053
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
54+
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
5155
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
5256
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
57+
import org.springframework.security.web.access.intercept.AuthorizationFilter;
5358
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
5459
import org.springframework.security.web.debug.DebugFilter;
5560
import org.springframework.security.web.firewall.HttpFirewall;
5661
import org.springframework.security.web.firewall.RequestRejectedHandler;
5762
import org.springframework.security.web.firewall.StrictHttpFirewall;
5863
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
5964
import org.springframework.security.web.util.matcher.RequestMatcher;
65+
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
6066
import org.springframework.util.Assert;
67+
import org.springframework.web.context.ServletContextAware;
6168
import org.springframework.web.filter.DelegatingFilterProxy;
6269

6370
/**
@@ -81,7 +88,7 @@
8188
* @see WebSecurityConfiguration
8289
*/
8390
public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
84-
implements SecurityBuilder<Filter>, ApplicationContextAware {
91+
implements SecurityBuilder<Filter>, ApplicationContextAware, ServletContextAware {
8592

8693
private final Log logger = LogFactory.getLog(getClass());
8794

@@ -108,6 +115,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
108115
private Runnable postBuildAction = () -> {
109116
};
110117

118+
private ServletContext servletContext;
119+
111120
/**
112121
* Creates a new instance
113122
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
@@ -252,6 +261,8 @@ public WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() {
252261
* {@link WebSecurityConfigurerAdapter}.
253262
* @param securityInterceptor the {@link FilterSecurityInterceptor} to use
254263
* @return the {@link WebSecurity} for further customizations
264+
* @deprecated Use {@link #privilegeEvaluator(WebInvocationPrivilegeEvaluator)}
265+
* instead
255266
*/
256267
public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
257268
this.filterSecurityInterceptor = securityInterceptor;
@@ -278,11 +289,22 @@ protected Filter performBuild() throws Exception {
278289
+ ".addSecurityFilterChainBuilder directly");
279290
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
280291
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
292+
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
281293
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
282-
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
294+
SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
295+
securityFilterChains.add(securityFilterChain);
296+
requestMatcherPrivilegeEvaluatorsEntries
297+
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
283298
}
284299
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
285-
securityFilterChains.add(securityFilterChainBuilder.build());
300+
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
301+
securityFilterChains.add(securityFilterChain);
302+
requestMatcherPrivilegeEvaluatorsEntries
303+
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
304+
}
305+
if (this.privilegeEvaluator == null) {
306+
this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
307+
requestMatcherPrivilegeEvaluatorsEntries);
286308
}
287309
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
288310
if (this.httpFirewall != null) {
@@ -306,6 +328,26 @@ protected Filter performBuild() throws Exception {
306328
return result;
307329
}
308330

331+
private RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> getRequestMatcherPrivilegeEvaluatorsEntry(
332+
SecurityFilterChain securityFilterChain) {
333+
List<WebInvocationPrivilegeEvaluator> privilegeEvaluators = new ArrayList<>();
334+
for (Filter filter : securityFilterChain.getFilters()) {
335+
if (filter instanceof FilterSecurityInterceptor) {
336+
DefaultWebInvocationPrivilegeEvaluator defaultWebInvocationPrivilegeEvaluator = new DefaultWebInvocationPrivilegeEvaluator(
337+
(FilterSecurityInterceptor) filter);
338+
defaultWebInvocationPrivilegeEvaluator.setServletContext(this.servletContext);
339+
privilegeEvaluators.add(defaultWebInvocationPrivilegeEvaluator);
340+
continue;
341+
}
342+
if (filter instanceof AuthorizationFilter) {
343+
AuthorizationManager<HttpServletRequest> authorizationManager = ((AuthorizationFilter) filter)
344+
.getAuthorizationManager();
345+
privilegeEvaluators.add(new AuthorizationManagerWebInvocationPrivilegeEvaluator(authorizationManager));
346+
}
347+
}
348+
return new RequestMatcherEntry<>(securityFilterChain::matches, privilegeEvaluators);
349+
}
350+
309351
@Override
310352
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
311353
this.defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
@@ -333,6 +375,11 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
333375
}
334376
}
335377

378+
@Override
379+
public void setServletContext(ServletContext servletContext) {
380+
this.servletContext = servletContext;
381+
}
382+
336383
/**
337384
* An {@link IgnoredRequestConfigurer} that allows optionally configuring the
338385
* {@link MvcRequestMatcher#setMethod(HttpMethod)}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ public Filter springSecurityFilterChain() throws Exception {
127127
}
128128

129129
/**
130-
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP
131-
* tag support.
130+
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary to evaluate
131+
* privileges for a given web URI
132132
* @return the {@link WebInvocationPrivilegeEvaluator}
133133
*/
134134
@Bean

config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java

+191-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@
3333
import org.springframework.context.annotation.Bean;
3434
import org.springframework.context.annotation.Configuration;
3535
import org.springframework.context.annotation.Import;
36+
import org.springframework.core.Ordered;
3637
import org.springframework.core.annotation.Order;
3738
import org.springframework.expression.EvaluationContext;
3839
import org.springframework.expression.Expression;
@@ -62,7 +63,7 @@
6263
import org.springframework.security.web.FilterChainProxy;
6364
import org.springframework.security.web.FilterInvocation;
6465
import org.springframework.security.web.SecurityFilterChain;
65-
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
66+
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
6667
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
6768
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
6869
import org.springframework.test.web.servlet.MockMvc;
@@ -84,6 +85,7 @@
8485
* @author Rob Winch
8586
* @author Joe Grandja
8687
* @author Evgeniy Cheban
88+
* @author Marcus Da Coregio
8789
*/
8890
@ExtendWith(SpringTestContextExtension.class)
8991
public class WebSecurityConfigurationTests {
@@ -218,18 +220,18 @@ public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEv
218220
}
219221

220222
@Test
221-
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenDefaultIsRegistered() {
223+
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenRequestMatcherIsRegistered() {
222224
this.spring.register(WebInvocationPrivilegeEvaluatorDefaultsConfig.class).autowire();
223225
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
224-
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
226+
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
225227
}
226228

227229
@Test
228230
public void loadConfigWhenSecurityFilterChainBeanThenDefaultWebInvocationPrivilegeEvaluatorIsRegistered() {
229231
this.spring.register(AuthorizeRequestsFilterChainConfig.class).autowire();
230232

231233
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
232-
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
234+
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
233235
}
234236

235237
// SEC-2303
@@ -375,6 +377,69 @@ public void loadConfigWhenMultipleAuthenticationManagersAndWebSecurityConfigurer
375377
assertThat(filterChains.get(1).matches(request)).isTrue();
376378
}
377379

380+
@Test
381+
public void loadConfigWhenTwoSecurityFilterChainsThenRequestMatcherDelegatingWebInvocationPrivilegeEvaluator() {
382+
this.spring.register(TwoSecurityFilterChainConfig.class).autowire();
383+
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
384+
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
385+
}
386+
387+
@Test
388+
public void loadConfigWhenTwoSecurityFilterChainDebugThenRequestMatcherDelegatingWebInvocationPrivilegeEvaluator() {
389+
this.spring.register(TwoSecurityFilterChainConfig.class).autowire();
390+
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
391+
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
392+
}
393+
394+
// gh-10554
395+
@Test
396+
public void loadConfigWhenMultipleSecurityFilterChainsThenWebInvocationPrivilegeEvaluatorApplySecurity() {
397+
this.spring.register(MultipleSecurityFilterChainConfig.class).autowire();
398+
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.spring.getContext()
399+
.getBean(WebInvocationPrivilegeEvaluator.class);
400+
assertUserPermissions(privilegeEvaluator);
401+
assertAdminPermissions(privilegeEvaluator);
402+
assertAnotherUserPermission(privilegeEvaluator);
403+
}
404+
405+
// gh-10554
406+
@Test
407+
public void loadConfigWhenMultipleSecurityFilterChainAndIgnoringThenWebInvocationPrivilegeEvaluatorAcceptsNullAuthenticationOnIgnored() {
408+
this.spring.register(MultipleSecurityFilterChainIgnoringConfig.class).autowire();
409+
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.spring.getContext()
410+
.getBean(WebInvocationPrivilegeEvaluator.class);
411+
assertUserPermissions(privilegeEvaluator);
412+
assertAdminPermissions(privilegeEvaluator);
413+
assertAnotherUserPermission(privilegeEvaluator);
414+
// null authentication
415+
assertThat(privilegeEvaluator.isAllowed("/user", null)).isFalse();
416+
assertThat(privilegeEvaluator.isAllowed("/admin", null)).isFalse();
417+
assertThat(privilegeEvaluator.isAllowed("/another", null)).isFalse();
418+
assertThat(privilegeEvaluator.isAllowed("/ignoring1", null)).isTrue();
419+
assertThat(privilegeEvaluator.isAllowed("/ignoring1/child", null)).isTrue();
420+
}
421+
422+
private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
423+
Authentication anotherUser = new TestingAuthenticationToken("anotherUser", "password", "ROLE_ANOTHER");
424+
assertThat(privilegeEvaluator.isAllowed("/user", anotherUser)).isFalse();
425+
assertThat(privilegeEvaluator.isAllowed("/admin", anotherUser)).isFalse();
426+
assertThat(privilegeEvaluator.isAllowed("/another", anotherUser)).isTrue();
427+
}
428+
429+
private void assertAdminPermissions(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
430+
Authentication admin = new TestingAuthenticationToken("admin", "password", "ROLE_ADMIN");
431+
assertThat(privilegeEvaluator.isAllowed("/user", admin)).isFalse();
432+
assertThat(privilegeEvaluator.isAllowed("/admin", admin)).isTrue();
433+
assertThat(privilegeEvaluator.isAllowed("/another", admin)).isTrue();
434+
}
435+
436+
private void assertUserPermissions(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
437+
Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
438+
assertThat(privilegeEvaluator.isAllowed("/user", user)).isTrue();
439+
assertThat(privilegeEvaluator.isAllowed("/admin", user)).isFalse();
440+
assertThat(privilegeEvaluator.isAllowed("/another", user)).isTrue();
441+
}
442+
378443
@EnableWebSecurity
379444
@Import(AuthenticationTestConfiguration.class)
380445
static class SortedWebSecurityConfigurerAdaptersConfig {
@@ -1008,4 +1073,125 @@ protected AuthenticationManager authenticationManager() {
10081073

10091074
}
10101075

1076+
@EnableWebSecurity
1077+
static class TwoSecurityFilterChainConfig {
1078+
1079+
@Bean
1080+
@Order(Ordered.HIGHEST_PRECEDENCE)
1081+
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
1082+
// @formatter:off
1083+
http
1084+
.requestMatchers((requests) -> requests.antMatchers("/path1/**"))
1085+
.authorizeRequests((requests) -> requests.anyRequest().authenticated());
1086+
// @formatter:on
1087+
return http.build();
1088+
}
1089+
1090+
@Bean
1091+
@Order(Ordered.LOWEST_PRECEDENCE)
1092+
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
1093+
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
1094+
return http.build();
1095+
}
1096+
1097+
}
1098+
1099+
@EnableWebSecurity(debug = true)
1100+
static class TwoSecurityFilterChainDebugConfig {
1101+
1102+
@Bean
1103+
@Order(Ordered.HIGHEST_PRECEDENCE)
1104+
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
1105+
// @formatter:off
1106+
http
1107+
.requestMatchers((requests) -> requests.antMatchers("/path1/**"))
1108+
.authorizeRequests((requests) -> requests.anyRequest().authenticated());
1109+
// @formatter:on
1110+
return http.build();
1111+
}
1112+
1113+
@Bean
1114+
@Order(Ordered.LOWEST_PRECEDENCE)
1115+
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
1116+
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
1117+
return http.build();
1118+
}
1119+
1120+
}
1121+
1122+
@EnableWebSecurity
1123+
@Import(AuthenticationTestConfiguration.class)
1124+
static class MultipleSecurityFilterChainConfig {
1125+
1126+
@Bean
1127+
@Order(Ordered.HIGHEST_PRECEDENCE)
1128+
public SecurityFilterChain notAuthorized(HttpSecurity http) throws Exception {
1129+
// @formatter:off
1130+
http
1131+
.requestMatchers((requests) -> requests.antMatchers("/user"))
1132+
.authorizeRequests((requests) -> requests.anyRequest().hasRole("USER"));
1133+
// @formatter:on
1134+
return http.build();
1135+
}
1136+
1137+
@Bean
1138+
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
1139+
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
1140+
// @formatter:off
1141+
http
1142+
.requestMatchers((requests) -> requests.antMatchers("/admin"))
1143+
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ADMIN"));
1144+
// @formatter:on
1145+
return http.build();
1146+
}
1147+
1148+
@Bean
1149+
@Order(Ordered.LOWEST_PRECEDENCE)
1150+
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
1151+
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
1152+
return http.build();
1153+
}
1154+
1155+
}
1156+
1157+
@EnableWebSecurity
1158+
@Import(AuthenticationTestConfiguration.class)
1159+
static class MultipleSecurityFilterChainIgnoringConfig {
1160+
1161+
@Bean
1162+
public WebSecurityCustomizer webSecurityCustomizer() {
1163+
return (web) -> web.ignoring().antMatchers("/ignoring1/**");
1164+
}
1165+
1166+
@Bean
1167+
@Order(Ordered.HIGHEST_PRECEDENCE)
1168+
public SecurityFilterChain notAuthorized(HttpSecurity http) throws Exception {
1169+
// @formatter:off
1170+
http
1171+
.requestMatchers((requests) -> requests.antMatchers("/user"))
1172+
.authorizeRequests((requests) -> requests.anyRequest().hasRole("USER"));
1173+
// @formatter:on
1174+
return http.build();
1175+
}
1176+
1177+
@Bean
1178+
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
1179+
public SecurityFilterChain admin(HttpSecurity http) throws Exception {
1180+
// @formatter:off
1181+
http
1182+
.requestMatchers((requests) -> requests.antMatchers("/admin"))
1183+
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ADMIN"));
1184+
// @formatter:on
1185+
return http.build();
1186+
}
1187+
1188+
@Bean
1189+
@Order(Ordered.LOWEST_PRECEDENCE)
1190+
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
1191+
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
1192+
return http.build();
1193+
}
1194+
1195+
}
1196+
10111197
}

0 commit comments

Comments
 (0)