Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Option to Filter All Dispatcher Types #11094

Merged
merged 1 commit into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public void configure(H http) {
AuthorizationManager<HttpServletRequest> authorizationManager = this.registry.createAuthorizationManager();
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
authorizationFilter.setAuthorizationEventPublisher(this.publisher);
authorizationFilter.setShouldFilterAllDispatcherTypes(this.registry.shouldFilterAllDispatcherTypes);
http.addFilter(postProcess(authorizationFilter));
}

Expand Down Expand Up @@ -117,6 +118,8 @@ public final class AuthorizationManagerRequestMatcherRegistry

private int mappingCount;

private boolean shouldFilterAllDispatcherTypes = false;

private AuthorizationManagerRequestMatcherRegistry(ApplicationContext context) {
setApplicationContext(context);
}
Expand Down Expand Up @@ -170,6 +173,19 @@ public AuthorizationManagerRequestMatcherRegistry withObjectPostProcessor(
return this;
}

/**
* Sets whether all dispatcher types should be filtered.
* @param shouldFilter should filter all dispatcher types. Default is
* {@code false}
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
* customizations
* @since 5.7
*/
public AuthorizationManagerRequestMatcherRegistry shouldFilterAllDispatcherTypes(boolean shouldFilter) {
this.shouldFilterAllDispatcherTypes = shouldFilter;
return this;
}

/**
* Return the {@link HttpSecurityBuilder} when done using the
* {@link AuthorizeHttpRequestsConfigurer}. This is useful for method chaining.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,25 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
}
----
====

By default, the `AuthorizationFilter` does not apply to `DispatcherType.ERROR` and `DispatcherType.ASYNC`.
We can configure Spring Security to apply the authorization rules to all dispatcher types by using the `shouldFilterAllDispatcherTypes` method:

.Set shouldFilterAllDispatcherTypes to true
====
.Java
[source,java,role="primary"]
----
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.shouldFilterAllDispatcherTypes(true)
.anyRequest.authenticated()
)
// ...

return http.build();
}
----
====
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public class AuthorizationFilter extends OncePerRequestFilter {

private AuthorizationEventPublisher eventPublisher = AuthorizationFilter::noPublish;

private boolean shouldFilterAllDispatcherTypes = false;

/**
* Creates an instance.
* @param authorizationManager the {@link AuthorizationManager} to use
Expand Down Expand Up @@ -80,6 +82,22 @@ private Authentication getAuthentication() {
return authentication;
}

@Override
protected void doFilterNestedErrorDispatch(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
doFilterInternal(request, response, filterChain);
}

@Override
protected boolean shouldNotFilterAsyncDispatch() {
return !this.shouldFilterAllDispatcherTypes;
}

@Override
protected boolean shouldNotFilterErrorDispatch() {
return !this.shouldFilterAllDispatcherTypes;
}

/**
* Use this {@link AuthorizationEventPublisher} to publish
* {@link AuthorizationDeniedEvent}s and {@link AuthorizationGrantedEvent}s.
Expand All @@ -99,6 +117,16 @@ public AuthorizationManager<HttpServletRequest> getAuthorizationManager() {
return this.authorizationManager;
}

/**
* Sets whether to filter all dispatcher types.
* @param shouldFilterAllDispatcherTypes should filter all dispatcher types. Default
* is {@code false}
* @since 5.7
*/
public void setShouldFilterAllDispatcherTypes(boolean shouldFilterAllDispatcherTypes) {
this.shouldFilterAllDispatcherTypes = shouldFilterAllDispatcherTypes;
}

private static <T> void noPublish(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
Expand All @@ -18,6 +18,7 @@

import java.util.function.Supplier;

import javax.servlet.DispatcherType;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;

Expand All @@ -38,6 +39,7 @@
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.web.util.WebUtils;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
Expand Down Expand Up @@ -165,4 +167,67 @@ public void doFilterWhenAuthorizationEventPublisherThenUses() throws Exception {
any(AuthorizationDecision.class));
}

@Test
public void doFilterWhenErrorThenDoNotFilter() throws Exception {
AuthorizationManager<HttpServletRequest> authorizationManager = mock(AuthorizationManager.class);
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path");
mockRequest.setDispatcherType(DispatcherType.ERROR);
mockRequest.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error");
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
FilterChain mockFilterChain = mock(FilterChain.class);

authorizationFilter.doFilter(mockRequest, mockResponse, mockFilterChain);
verifyNoInteractions(authorizationManager);
}

@Test
public void doFilterWhenErrorAndShouldFilterAllDispatcherTypesThenFilter() throws Exception {
AuthorizationManager<HttpServletRequest> authorizationManager = mock(AuthorizationManager.class);
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
authorizationFilter.setShouldFilterAllDispatcherTypes(true);
MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path");
mockRequest.setDispatcherType(DispatcherType.ERROR);
mockRequest.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error");
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
FilterChain mockFilterChain = mock(FilterChain.class);

authorizationFilter.doFilter(mockRequest, mockResponse, mockFilterChain);
verify(authorizationManager).check(any(Supplier.class), any(HttpServletRequest.class));
}

@Test
public void doFilterNestedErrorDispatchWhenAuthorizationManagerThenUses() throws Exception {
AuthorizationManager<HttpServletRequest> authorizationManager = mock(AuthorizationManager.class);
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
authorizationFilter.setShouldFilterAllDispatcherTypes(true);
MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path");
mockRequest.setDispatcherType(DispatcherType.ERROR);
mockRequest.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error");
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
FilterChain mockFilterChain = mock(FilterChain.class);

authorizationFilter.doFilterNestedErrorDispatch(mockRequest, mockResponse, mockFilterChain);
verify(authorizationManager).check(any(Supplier.class), any(HttpServletRequest.class));
}

@Test
public void doFilterNestedErrorDispatchWhenAuthorizationEventPublisherThenUses() throws Exception {
AuthorizationFilter authorizationFilter = new AuthorizationFilter(
AuthenticatedAuthorizationManager.authenticated());
MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path");
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
FilterChain mockFilterChain = mock(FilterChain.class);

SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
SecurityContextHolder.setContext(securityContext);

AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class);
authorizationFilter.setAuthorizationEventPublisher(eventPublisher);
authorizationFilter.doFilterNestedErrorDispatch(mockRequest, mockResponse, mockFilterChain);
verify(eventPublisher).publishAuthorizationEvent(any(Supplier.class), any(HttpServletRequest.class),
any(AuthorizationDecision.class));
}

}