Skip to content

Commit 3a1692f

Browse files
committed
Remove Direct Runtime Dependency on Access API
Issue gh-17847
1 parent 1093563 commit 3a1692f

File tree

8 files changed

+195
-65
lines changed

8 files changed

+195
-65
lines changed

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
import jakarta.servlet.Filter;
2424

2525
import org.springframework.security.web.access.ExceptionTranslationFilter;
26-
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
2726
import org.springframework.security.web.access.intercept.AuthorizationFilter;
28-
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
2927
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
3028
import org.springframework.security.web.authentication.AuthenticationFilter;
3129
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -78,7 +76,7 @@ final class FilterOrderRegistration {
7876
Step order = new Step(INITIAL_ORDER, ORDER_STEP);
7977
put(DisableEncodeUrlFilter.class, order.next());
8078
put(ForceEagerSessionCreationFilter.class, order.next());
81-
put(ChannelProcessingFilter.class, order.next());
79+
this.filterToOrder.put("org.springframework.security.web.access.channel.ChannelProcessingFilter", order.next());
8280
put(HttpsRedirectFilter.class, order.next());
8381
order.next(); // gh-8105
8482
put(WebAsyncManagerIntegrationFilter.class, order.next());
@@ -126,7 +124,8 @@ final class FilterOrderRegistration {
126124
order.next());
127125
put(SessionManagementFilter.class, order.next());
128126
put(ExceptionTranslationFilter.class, order.next());
129-
put(FilterSecurityInterceptor.class, order.next());
127+
this.filterToOrder.put("org.springframework.security.web.access.intercept.FilterSecurityInterceptor",
128+
order.next());
130129
put(AuthorizationFilter.class, order.next());
131130
put(SwitchUserFilter.class, order.next());
132131
}

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

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,28 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.List;
21+
import java.util.function.Supplier;
2122

2223
import io.micrometer.observation.ObservationRegistry;
2324
import jakarta.servlet.Filter;
2425
import jakarta.servlet.ServletContext;
2526
import jakarta.servlet.http.HttpServletRequest;
2627
import org.apache.commons.logging.Log;
2728
import org.apache.commons.logging.LogFactory;
29+
import org.jspecify.annotations.NullMarked;
30+
import org.jspecify.annotations.Nullable;
2831

2932
import org.springframework.beans.BeansException;
3033
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3134
import org.springframework.beans.factory.ObjectProvider;
3235
import org.springframework.context.ApplicationContext;
3336
import org.springframework.context.ApplicationContextAware;
3437
import org.springframework.core.ResolvableType;
38+
import org.springframework.expression.EvaluationContext;
3539
import org.springframework.security.access.PermissionEvaluator;
40+
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
3641
import org.springframework.security.access.expression.SecurityExpressionHandler;
42+
import org.springframework.security.access.expression.SecurityExpressionOperations;
3743
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
3844
import org.springframework.security.authorization.AuthorizationDecision;
3945
import org.springframework.security.authorization.AuthorizationManager;
@@ -46,6 +52,7 @@
4652
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
4753
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
4854
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
55+
import org.springframework.security.core.Authentication;
4956
import org.springframework.security.core.context.SecurityContext;
5057
import org.springframework.security.web.DefaultSecurityFilterChain;
5158
import org.springframework.security.web.FilterChainProxy;
@@ -58,7 +65,7 @@
5865
import org.springframework.security.web.access.PathPatternRequestTransformer;
5966
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
6067
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
61-
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
68+
import org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler;
6269
import org.springframework.security.web.access.intercept.AuthorizationFilter;
6370
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
6471
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
@@ -74,6 +81,7 @@
7481
import org.springframework.security.web.util.matcher.RequestMatcher;
7582
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
7683
import org.springframework.util.Assert;
84+
import org.springframework.util.ClassUtils;
7785
import org.springframework.web.context.ServletContextAware;
7886
import org.springframework.web.filter.DelegatingFilterProxy;
7987

@@ -99,6 +107,9 @@
99107
public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
100108
implements SecurityBuilder<Filter>, ApplicationContextAware, ServletContextAware {
101109

110+
private static final boolean USING_ACCESS = ClassUtils
111+
.isPresent("org.springframework.security.access.SecurityConfig", null);
112+
102113
private final Log logger = LogFactory.getLog(getClass());
103114

104115
private final List<RequestMatcher> ignoredRequests = new ArrayList<>();
@@ -122,9 +133,10 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
122133

123134
private HttpServletRequestTransformer privilegeEvaluatorRequestTransformer;
124135

125-
private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
136+
private DefaultHttpSecurityExpressionHandler defaultExpressionHandler = new DefaultHttpSecurityExpressionHandler();
126137

127-
private SecurityExpressionHandler<FilterInvocation> expressionHandler = this.defaultWebSecurityExpressionHandler;
138+
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new SecurityExpressionHandlerAdapter(
139+
this.defaultExpressionHandler);
128140

129141
private Runnable postBuildAction = () -> {
130142
};
@@ -240,7 +252,7 @@ public WebSecurity privilegeEvaluator(WebInvocationPrivilegeEvaluator privilegeE
240252

241253
/**
242254
* Set the {@link SecurityExpressionHandler} to be used. If this is not specified,
243-
* then a {@link DefaultWebSecurityExpressionHandler} will be used.
255+
* then a {@link DefaultHttpSecurityExpressionHandler} will be used.
244256
* @param expressionHandler the {@link SecurityExpressionHandler} to use
245257
* @return the {@link WebSecurity} for further customizations
246258
*/
@@ -361,19 +373,9 @@ private boolean addAuthorizationManager(SecurityFilterChain securityFilterChain,
361373
RequestMatcherDelegatingAuthorizationManager.Builder builder) {
362374
boolean mappings = false;
363375
for (Filter filter : securityFilterChain.getFilters()) {
364-
if (filter instanceof FilterSecurityInterceptor securityInterceptor) {
365-
DefaultWebInvocationPrivilegeEvaluator privilegeEvaluator = new DefaultWebInvocationPrivilegeEvaluator(
366-
securityInterceptor);
367-
privilegeEvaluator.setServletContext(this.servletContext);
368-
AuthorizationManager<RequestAuthorizationContext> authorizationManager = (authentication, context) -> {
369-
HttpServletRequest request = context.getRequest();
370-
boolean result = privilegeEvaluator.isAllowed(request.getContextPath(), request.getRequestURI(),
371-
request.getMethod(), authentication.get());
372-
return new AuthorizationDecision(result);
373-
};
374-
builder.add(securityFilterChain::matches, authorizationManager);
375-
mappings = true;
376-
continue;
376+
if (USING_ACCESS) {
377+
mappings = AccessComponents.addAuthorizationManager(filter, this.servletContext, builder,
378+
securityFilterChain);
377379
}
378380
if (filter instanceof AuthorizationFilter authorization) {
379381
AuthorizationManager<HttpServletRequest> authorizationManager = authorization.getAuthorizationManager();
@@ -388,15 +390,14 @@ private boolean addAuthorizationManager(SecurityFilterChain securityFilterChain,
388390

389391
@Override
390392
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
391-
this.defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
393+
this.defaultExpressionHandler.setApplicationContext(applicationContext);
392394
try {
393-
this.defaultWebSecurityExpressionHandler.setRoleHierarchy(applicationContext.getBean(RoleHierarchy.class));
395+
this.defaultExpressionHandler.setRoleHierarchy(applicationContext.getBean(RoleHierarchy.class));
394396
}
395397
catch (NoSuchBeanDefinitionException ex) {
396398
}
397399
try {
398-
this.defaultWebSecurityExpressionHandler
399-
.setPermissionEvaluator(applicationContext.getBean(PermissionEvaluator.class));
400+
this.defaultExpressionHandler.setPermissionEvaluator(applicationContext.getBean(PermissionEvaluator.class));
400401
}
401402
catch (NoSuchBeanDefinitionException ex) {
402403
}
@@ -463,4 +464,76 @@ public WebSecurity and() {
463464

464465
}
465466

467+
@NullMarked
468+
private static final class SecurityExpressionHandlerAdapter
469+
extends AbstractSecurityExpressionHandler<FilterInvocation> {
470+
471+
private final AbstractSecurityExpressionHandler<RequestAuthorizationContext> delegate;
472+
473+
private SecurityExpressionHandlerAdapter(
474+
AbstractSecurityExpressionHandler<RequestAuthorizationContext> delegate) {
475+
this.delegate = delegate;
476+
}
477+
478+
@Override
479+
public EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
480+
FilterInvocation invocation) {
481+
RequestAuthorizationContext context = new RequestAuthorizationContext(invocation.getRequest());
482+
return this.delegate.createEvaluationContext(authentication, context);
483+
}
484+
485+
@Override
486+
protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication,
487+
FilterInvocation invocation) {
488+
RequestAuthorizationContext context = new RequestAuthorizationContext(invocation.getRequest());
489+
Object operations = this.delegate.createEvaluationContext(authentication, context)
490+
.getRootObject()
491+
.getValue();
492+
Assert.isInstanceOf(SecurityExpressionOperations.class, operations,
493+
"createEvaluationContext must have a SecurityExpressionOperations instance as its root");
494+
return (SecurityExpressionOperations) operations;
495+
}
496+
497+
@Override
498+
public void setApplicationContext(ApplicationContext context) {
499+
this.delegate.setApplicationContext(context);
500+
super.setApplicationContext(context);
501+
}
502+
503+
@Override
504+
public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
505+
this.delegate.setPermissionEvaluator(permissionEvaluator);
506+
super.setPermissionEvaluator(permissionEvaluator);
507+
}
508+
509+
@Override
510+
public void setRoleHierarchy(@Nullable RoleHierarchy roleHierarchy) {
511+
this.delegate.setRoleHierarchy(roleHierarchy);
512+
super.setRoleHierarchy(roleHierarchy);
513+
}
514+
515+
}
516+
517+
private static final class AccessComponents {
518+
519+
private static boolean addAuthorizationManager(Filter filter, ServletContext servletContext,
520+
RequestMatcherDelegatingAuthorizationManager.Builder builder, SecurityFilterChain securityFilterChain) {
521+
if (filter instanceof FilterSecurityInterceptor securityInterceptor) {
522+
DefaultWebInvocationPrivilegeEvaluator privilegeEvaluator = new DefaultWebInvocationPrivilegeEvaluator(
523+
securityInterceptor);
524+
privilegeEvaluator.setServletContext(servletContext);
525+
AuthorizationManager<RequestAuthorizationContext> authorizationManager = (authentication, context) -> {
526+
HttpServletRequest request = context.getRequest();
527+
boolean result = privilegeEvaluator.isAllowed(request.getContextPath(), request.getRequestURI(),
528+
request.getMethod(), authentication.get());
529+
return new AuthorizationDecision(result);
530+
};
531+
builder.add(securityFilterChain::matches, authorizationManager);
532+
return true;
533+
}
534+
return false;
535+
}
536+
537+
}
538+
466539
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.security.web.access.intercept.AuthorizationFilter;
3030
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
3131
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
32+
import org.springframework.util.ClassUtils;
3233

3334
/**
3435
* A filter chain validator for filter chains built by {@link WebSecurity}
@@ -39,6 +40,9 @@
3940
*/
4041
final class WebSecurityFilterChainValidator implements FilterChainProxy.FilterChainValidator {
4142

43+
private static final boolean USING_ACCESS = ClassUtils
44+
.isPresent("org.springframework.security.access.SecurityConfig", null);
45+
4246
private final Log logger = LogFactory.getLog(getClass());
4347

4448
@Override
@@ -93,7 +97,7 @@ private void checkAuthorizationFilters(List<SecurityFilterChain> chains) {
9397
if (filter instanceof AuthorizationFilter) {
9498
authorizationFilter = filter;
9599
}
96-
if (filter instanceof FilterSecurityInterceptor) {
100+
if (USING_ACCESS && AccessComponents.isFilterSecurityInterceptor(filter)) {
97101
filterSecurityInterceptor = filter;
98102
}
99103
}
@@ -110,4 +114,12 @@ private void checkAuthorizationFilters(List<SecurityFilterChain> chains) {
110114
}
111115
}
112116

117+
private static final class AccessComponents {
118+
119+
private static boolean isFilterSecurityInterceptor(Filter filter) {
120+
return filter instanceof FilterSecurityInterceptor;
121+
}
122+
123+
}
124+
113125
}

0 commit comments

Comments
 (0)