Skip to content

Commit 7fea639

Browse files
Add Option to Filter All Dispatcher Types
Closes gh-11092
1 parent 6e6d472 commit 7fea639

File tree

4 files changed

+132
-1
lines changed

4 files changed

+132
-1
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public void configure(H http) {
8585
AuthorizationManager<HttpServletRequest> authorizationManager = this.registry.createAuthorizationManager();
8686
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
8787
authorizationFilter.setAuthorizationEventPublisher(this.publisher);
88+
authorizationFilter.setShouldFilterAllDispatcherTypes(this.registry.shouldFilterAllDispatcherTypes);
8889
http.addFilter(postProcess(authorizationFilter));
8990
}
9091

@@ -117,6 +118,8 @@ public final class AuthorizationManagerRequestMatcherRegistry
117118

118119
private int mappingCount;
119120

121+
private boolean shouldFilterAllDispatcherTypes = false;
122+
120123
private AuthorizationManagerRequestMatcherRegistry(ApplicationContext context) {
121124
setApplicationContext(context);
122125
}
@@ -170,6 +173,19 @@ public AuthorizationManagerRequestMatcherRegistry withObjectPostProcessor(
170173
return this;
171174
}
172175

176+
/**
177+
* Sets whether all dispatcher types should be filtered.
178+
* @param shouldFilter should filter all dispatcher types. Default is
179+
* {@code false}
180+
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
181+
* customizations
182+
* @since 5.7
183+
*/
184+
public AuthorizationManagerRequestMatcherRegistry shouldFilterAllDispatcherTypes(boolean shouldFilter) {
185+
this.shouldFilterAllDispatcherTypes = shouldFilter;
186+
return this;
187+
}
188+
173189
/**
174190
* Return the {@link HttpSecurityBuilder} when done using the
175191
* {@link AuthorizeHttpRequestsConfigurer}. This is useful for method chaining.

docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,25 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
169169
}
170170
----
171171
====
172+
173+
By default, the `AuthorizationFilter` does not apply to `DispatcherType.ERROR` and `DispatcherType.ASYNC`.
174+
We can configure Spring Security to apply the authorization rules to all dispatcher types by using the `shouldFilterAllDispatcherTypes` method:
175+
176+
.Set shouldFilterAllDispatcherTypes to true
177+
====
178+
.Java
179+
[source,java,role="primary"]
180+
----
181+
@Bean
182+
SecurityFilterChain web(HttpSecurity http) throws Exception {
183+
http
184+
.authorizeHttpRequests((authorize) -> authorize
185+
.shouldFilterAllDispatcherTypes(true)
186+
.anyRequest.authenticated()
187+
)
188+
// ...
189+
190+
return http.build();
191+
}
192+
----
193+
====

web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public class AuthorizationFilter extends OncePerRequestFilter {
5050

5151
private AuthorizationEventPublisher eventPublisher = AuthorizationFilter::noPublish;
5252

53+
private boolean shouldFilterAllDispatcherTypes = false;
54+
5355
/**
5456
* Creates an instance.
5557
* @param authorizationManager the {@link AuthorizationManager} to use
@@ -80,6 +82,22 @@ private Authentication getAuthentication() {
8082
return authentication;
8183
}
8284

85+
@Override
86+
protected void doFilterNestedErrorDispatch(HttpServletRequest request, HttpServletResponse response,
87+
FilterChain filterChain) throws ServletException, IOException {
88+
doFilterInternal(request, response, filterChain);
89+
}
90+
91+
@Override
92+
protected boolean shouldNotFilterAsyncDispatch() {
93+
return !this.shouldFilterAllDispatcherTypes;
94+
}
95+
96+
@Override
97+
protected boolean shouldNotFilterErrorDispatch() {
98+
return !this.shouldFilterAllDispatcherTypes;
99+
}
100+
83101
/**
84102
* Use this {@link AuthorizationEventPublisher} to publish
85103
* {@link AuthorizationDeniedEvent}s and {@link AuthorizationGrantedEvent}s.
@@ -99,6 +117,16 @@ public AuthorizationManager<HttpServletRequest> getAuthorizationManager() {
99117
return this.authorizationManager;
100118
}
101119

120+
/**
121+
* Sets whether to filter all dispatcher types.
122+
* @param shouldFilterAllDispatcherTypes should filter all dispatcher types. Default
123+
* is {@code false}
124+
* @since 5.7
125+
*/
126+
public void setShouldFilterAllDispatcherTypes(boolean shouldFilterAllDispatcherTypes) {
127+
this.shouldFilterAllDispatcherTypes = shouldFilterAllDispatcherTypes;
128+
}
129+
102130
private static <T> void noPublish(Supplier<Authentication> authentication, T object,
103131
AuthorizationDecision decision) {
104132

web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -18,6 +18,7 @@
1818

1919
import java.util.function.Supplier;
2020

21+
import javax.servlet.DispatcherType;
2122
import javax.servlet.FilterChain;
2223
import javax.servlet.http.HttpServletRequest;
2324

@@ -38,6 +39,7 @@
3839
import org.springframework.security.core.context.SecurityContext;
3940
import org.springframework.security.core.context.SecurityContextHolder;
4041
import org.springframework.security.core.context.SecurityContextImpl;
42+
import org.springframework.web.util.WebUtils;
4143

4244
import static org.assertj.core.api.Assertions.assertThat;
4345
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -165,4 +167,67 @@ public void doFilterWhenAuthorizationEventPublisherThenUses() throws Exception {
165167
any(AuthorizationDecision.class));
166168
}
167169

170+
@Test
171+
public void doFilterWhenErrorThenDoNotFilter() throws Exception {
172+
AuthorizationManager<HttpServletRequest> authorizationManager = mock(AuthorizationManager.class);
173+
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
174+
MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path");
175+
mockRequest.setDispatcherType(DispatcherType.ERROR);
176+
mockRequest.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error");
177+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
178+
FilterChain mockFilterChain = mock(FilterChain.class);
179+
180+
authorizationFilter.doFilter(mockRequest, mockResponse, mockFilterChain);
181+
verifyNoInteractions(authorizationManager);
182+
}
183+
184+
@Test
185+
public void doFilterWhenErrorAndShouldFilterAllDispatcherTypesThenFilter() throws Exception {
186+
AuthorizationManager<HttpServletRequest> authorizationManager = mock(AuthorizationManager.class);
187+
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
188+
authorizationFilter.setShouldFilterAllDispatcherTypes(true);
189+
MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path");
190+
mockRequest.setDispatcherType(DispatcherType.ERROR);
191+
mockRequest.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error");
192+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
193+
FilterChain mockFilterChain = mock(FilterChain.class);
194+
195+
authorizationFilter.doFilter(mockRequest, mockResponse, mockFilterChain);
196+
verify(authorizationManager).check(any(Supplier.class), any(HttpServletRequest.class));
197+
}
198+
199+
@Test
200+
public void doFilterNestedErrorDispatchWhenAuthorizationManagerThenUses() throws Exception {
201+
AuthorizationManager<HttpServletRequest> authorizationManager = mock(AuthorizationManager.class);
202+
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
203+
authorizationFilter.setShouldFilterAllDispatcherTypes(true);
204+
MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path");
205+
mockRequest.setDispatcherType(DispatcherType.ERROR);
206+
mockRequest.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error");
207+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
208+
FilterChain mockFilterChain = mock(FilterChain.class);
209+
210+
authorizationFilter.doFilterNestedErrorDispatch(mockRequest, mockResponse, mockFilterChain);
211+
verify(authorizationManager).check(any(Supplier.class), any(HttpServletRequest.class));
212+
}
213+
214+
@Test
215+
public void doFilterNestedErrorDispatchWhenAuthorizationEventPublisherThenUses() throws Exception {
216+
AuthorizationFilter authorizationFilter = new AuthorizationFilter(
217+
AuthenticatedAuthorizationManager.authenticated());
218+
MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path");
219+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
220+
FilterChain mockFilterChain = mock(FilterChain.class);
221+
222+
SecurityContext securityContext = new SecurityContextImpl();
223+
securityContext.setAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
224+
SecurityContextHolder.setContext(securityContext);
225+
226+
AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class);
227+
authorizationFilter.setAuthorizationEventPublisher(eventPublisher);
228+
authorizationFilter.doFilterNestedErrorDispatch(mockRequest, mockResponse, mockFilterChain);
229+
verify(eventPublisher).publishAuthorizationEvent(any(Supplier.class), any(HttpServletRequest.class),
230+
any(AuthorizationDecision.class));
231+
}
232+
168233
}

0 commit comments

Comments
 (0)