Skip to content

Commit 6530777

Browse files
author
Steve Riesenberg
committed
Merge branch '5.5.x' into 5.6.x
Closes gh-dry-run
2 parents 26a51ee + 1f481aa commit 6530777

File tree

2 files changed

+215
-7
lines changed

2 files changed

+215
-7
lines changed

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

Lines changed: 91 additions & 6 deletions
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,8 +18,11 @@
1818

1919
import java.io.IOException;
2020

21+
import javax.servlet.DispatcherType;
2122
import javax.servlet.FilterChain;
2223
import javax.servlet.ServletException;
24+
import javax.servlet.ServletRequest;
25+
import javax.servlet.ServletResponse;
2326
import javax.servlet.http.HttpServletRequest;
2427
import javax.servlet.http.HttpServletResponse;
2528

@@ -28,7 +31,7 @@
2831
import org.springframework.security.core.Authentication;
2932
import org.springframework.security.core.context.SecurityContextHolder;
3033
import org.springframework.util.Assert;
31-
import org.springframework.web.filter.OncePerRequestFilter;
34+
import org.springframework.web.filter.GenericFilterBean;
3235

3336
/**
3437
* An authorization filter that restricts access to the URL using
@@ -37,10 +40,16 @@
3740
* @author Evgeniy Cheban
3841
* @since 5.5
3942
*/
40-
public class AuthorizationFilter extends OncePerRequestFilter {
43+
public class AuthorizationFilter extends GenericFilterBean {
4144

4245
private final AuthorizationManager<HttpServletRequest> authorizationManager;
4346

47+
private boolean observeOncePerRequest = true;
48+
49+
private boolean filterErrorDispatch = false;
50+
51+
private boolean filterAsyncDispatch = false;
52+
4453
/**
4554
* Creates an instance.
4655
* @param authorizationManager the {@link AuthorizationManager} to use
@@ -51,11 +60,53 @@ public AuthorizationFilter(AuthorizationManager<HttpServletRequest> authorizatio
5160
}
5261

5362
@Override
54-
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
63+
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
5564
throws ServletException, IOException {
5665

57-
this.authorizationManager.verify(this::getAuthentication, request);
58-
filterChain.doFilter(request, response);
66+
HttpServletRequest request = (HttpServletRequest) servletRequest;
67+
HttpServletResponse response = (HttpServletResponse) servletResponse;
68+
69+
if (this.observeOncePerRequest && isApplied(request)) {
70+
chain.doFilter(request, response);
71+
return;
72+
}
73+
74+
if (skipDispatch(request)) {
75+
chain.doFilter(request, response);
76+
return;
77+
}
78+
79+
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
80+
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
81+
try {
82+
this.authorizationManager.verify(this::getAuthentication, request);
83+
chain.doFilter(request, response);
84+
}
85+
finally {
86+
request.removeAttribute(alreadyFilteredAttributeName);
87+
}
88+
}
89+
90+
private boolean skipDispatch(HttpServletRequest request) {
91+
if (DispatcherType.ERROR.equals(request.getDispatcherType()) && !this.filterErrorDispatch) {
92+
return true;
93+
}
94+
if (DispatcherType.ASYNC.equals(request.getDispatcherType()) && !this.filterAsyncDispatch) {
95+
return true;
96+
}
97+
return false;
98+
}
99+
100+
private boolean isApplied(HttpServletRequest request) {
101+
return request.getAttribute(getAlreadyFilteredAttributeName()) != null;
102+
}
103+
104+
private String getAlreadyFilteredAttributeName() {
105+
String name = getFilterName();
106+
if (name == null) {
107+
name = getClass().getName();
108+
}
109+
return name + ".APPLIED";
59110
}
60111

61112
private Authentication getAuthentication() {
@@ -75,4 +126,38 @@ public AuthorizationManager<HttpServletRequest> getAuthorizationManager() {
75126
return this.authorizationManager;
76127
}
77128

129+
public boolean isObserveOncePerRequest() {
130+
return this.observeOncePerRequest;
131+
}
132+
133+
/**
134+
* Sets whether this filter apply only once per request. By default, this is
135+
* <code>true</code>, meaning the filter will only execute once per request. Sometimes
136+
* users may wish it to execute more than once per request, such as when JSP forwards
137+
* are being used and filter security is desired on each included fragment of the HTTP
138+
* request.
139+
* @param observeOncePerRequest whether the filter should only be applied once per
140+
* request
141+
*/
142+
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
143+
this.observeOncePerRequest = observeOncePerRequest;
144+
}
145+
146+
/**
147+
* If set to true, the filter will be applied to error dispatcher. Defaults to false.
148+
* @param filterErrorDispatch whether the filter should be applied to error dispatcher
149+
*/
150+
public void setFilterErrorDispatch(boolean filterErrorDispatch) {
151+
this.filterErrorDispatch = filterErrorDispatch;
152+
}
153+
154+
/**
155+
* If set to true, the filter will be applied to the async dispatcher. Defaults to
156+
* false.
157+
* @param filterAsyncDispatch whether the filter should be applied to async dispatch
158+
*/
159+
public void setFilterAsyncDispatch(boolean filterAsyncDispatch) {
160+
this.filterAsyncDispatch = filterAsyncDispatch;
161+
}
162+
78163
}

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

Lines changed: 124 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.
@@ -16,15 +16,20 @@
1616

1717
package org.springframework.security.web.access.intercept;
1818

19+
import java.io.IOException;
1920
import java.util.function.Supplier;
2021

22+
import javax.servlet.DispatcherType;
2123
import javax.servlet.FilterChain;
24+
import javax.servlet.ServletException;
2225
import javax.servlet.http.HttpServletRequest;
2326

2427
import org.junit.jupiter.api.AfterEach;
28+
import org.junit.jupiter.api.BeforeEach;
2529
import org.junit.jupiter.api.Test;
2630
import org.mockito.ArgumentCaptor;
2731

32+
import org.springframework.mock.web.MockFilterChain;
2833
import org.springframework.mock.web.MockHttpServletRequest;
2934
import org.springframework.mock.web.MockHttpServletResponse;
3035
import org.springframework.security.access.AccessDeniedException;
@@ -36,13 +41,15 @@
3641
import org.springframework.security.core.context.SecurityContext;
3742
import org.springframework.security.core.context.SecurityContextHolder;
3843
import org.springframework.security.core.context.SecurityContextImpl;
44+
import org.springframework.test.util.ReflectionTestUtils;
3945

4046
import static org.assertj.core.api.Assertions.assertThat;
4147
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4248
import static org.mockito.ArgumentMatchers.any;
4349
import static org.mockito.ArgumentMatchers.eq;
4450
import static org.mockito.BDDMockito.willThrow;
4551
import static org.mockito.Mockito.mock;
52+
import static org.mockito.Mockito.spy;
4653
import static org.mockito.Mockito.verify;
4754
import static org.mockito.Mockito.verifyNoInteractions;
4855

@@ -53,6 +60,24 @@
5360
*/
5461
public class AuthorizationFilterTests {
5562

63+
private static final String ALREADY_FILTERED_ATTRIBUTE_NAME = "org.springframework.security.web.access.intercept.AuthorizationFilter.APPLIED";
64+
65+
private AuthorizationFilter filter;
66+
67+
private AuthorizationManager<HttpServletRequest> authorizationManager;
68+
69+
private MockHttpServletRequest request = new MockHttpServletRequest();
70+
71+
private final MockHttpServletResponse response = new MockHttpServletResponse();
72+
73+
private final FilterChain chain = new MockFilterChain();
74+
75+
@BeforeEach
76+
public void setup() {
77+
this.authorizationManager = mock(AuthorizationManager.class);
78+
this.filter = new AuthorizationFilter(this.authorizationManager);
79+
}
80+
5681
@AfterEach
5782
public void tearDown() {
5883
SecurityContextHolder.clearContext();
@@ -132,4 +157,102 @@ public void getAuthorizationManager() {
132157
assertThat(authorizationFilter.getAuthorizationManager()).isSameAs(authorizationManager);
133158
}
134159

160+
@Test
161+
public void doFilterWhenObserveOncePerRequestTrueAndIsAppliedThenNotInvoked() throws ServletException, IOException {
162+
setIsAppliedTrue();
163+
this.filter.setObserveOncePerRequest(true);
164+
this.filter.doFilter(this.request, this.response, this.chain);
165+
verifyNoInteractions(this.authorizationManager);
166+
}
167+
168+
@Test
169+
public void doFilterWhenObserveOncePerRequestTrueAndNotAppliedThenInvoked() throws ServletException, IOException {
170+
this.filter.setObserveOncePerRequest(true);
171+
this.filter.doFilter(this.request, this.response, this.chain);
172+
verify(this.authorizationManager).verify(any(), any());
173+
}
174+
175+
@Test
176+
public void doFilterWhenObserveOncePerRequestFalseAndIsAppliedThenInvoked() throws ServletException, IOException {
177+
setIsAppliedTrue();
178+
this.filter.setObserveOncePerRequest(false);
179+
this.filter.doFilter(this.request, this.response, this.chain);
180+
verify(this.authorizationManager).verify(any(), any());
181+
}
182+
183+
@Test
184+
public void doFilterWhenObserveOncePerRequestFalseAndNotAppliedThenInvoked() throws ServletException, IOException {
185+
this.filter.setObserveOncePerRequest(false);
186+
this.filter.doFilter(this.request, this.response, this.chain);
187+
verify(this.authorizationManager).verify(any(), any());
188+
}
189+
190+
@Test
191+
public void doFilterWhenFilterErrorDispatchFalseAndIsErrorThenNotInvoked() throws ServletException, IOException {
192+
this.request.setDispatcherType(DispatcherType.ERROR);
193+
this.filter.setFilterErrorDispatch(false);
194+
this.filter.doFilter(this.request, this.response, this.chain);
195+
verifyNoInteractions(this.authorizationManager);
196+
}
197+
198+
@Test
199+
public void doFilterWhenFilterErrorDispatchTrueAndIsErrorThenInvoked() throws ServletException, IOException {
200+
this.request.setDispatcherType(DispatcherType.ERROR);
201+
this.filter.setFilterErrorDispatch(true);
202+
this.filter.doFilter(this.request, this.response, this.chain);
203+
verify(this.authorizationManager).verify(any(), any());
204+
}
205+
206+
@Test
207+
public void doFilterWhenFilterThenSetAlreadyFilteredAttribute() throws ServletException, IOException {
208+
this.request = mock(MockHttpServletRequest.class);
209+
this.filter.doFilter(this.request, this.response, this.chain);
210+
verify(this.request).setAttribute(ALREADY_FILTERED_ATTRIBUTE_NAME, Boolean.TRUE);
211+
}
212+
213+
@Test
214+
public void doFilterWhenFilterThenRemoveAlreadyFilteredAttribute() throws ServletException, IOException {
215+
this.request = spy(MockHttpServletRequest.class);
216+
this.filter.doFilter(this.request, this.response, this.chain);
217+
verify(this.request).setAttribute(ALREADY_FILTERED_ATTRIBUTE_NAME, Boolean.TRUE);
218+
assertThat(this.request.getAttribute(ALREADY_FILTERED_ATTRIBUTE_NAME)).isNull();
219+
}
220+
221+
@Test
222+
public void doFilterWhenFilterAsyncDispatchTrueAndIsAsyncThenInvoked() throws ServletException, IOException {
223+
this.request.setDispatcherType(DispatcherType.ASYNC);
224+
this.filter.setFilterAsyncDispatch(true);
225+
this.filter.doFilter(this.request, this.response, this.chain);
226+
verify(this.authorizationManager).verify(any(), any());
227+
}
228+
229+
@Test
230+
public void doFilterWhenFilterAsyncDispatchFalseAndIsAsyncThenNotInvoked() throws ServletException, IOException {
231+
this.request.setDispatcherType(DispatcherType.ASYNC);
232+
this.filter.setFilterAsyncDispatch(false);
233+
this.filter.doFilter(this.request, this.response, this.chain);
234+
verifyNoInteractions(this.authorizationManager);
235+
}
236+
237+
@Test
238+
public void filterWhenFilterErrorDispatchDefaultThenFalse() {
239+
Boolean filterErrorDispatch = (Boolean) ReflectionTestUtils.getField(this.filter, "filterErrorDispatch");
240+
assertThat(filterErrorDispatch).isFalse();
241+
}
242+
243+
@Test
244+
public void filterWhenFilterAsyncDispatchDefaultThenFalse() {
245+
Boolean filterAsyncDispatch = (Boolean) ReflectionTestUtils.getField(this.filter, "filterAsyncDispatch");
246+
assertThat(filterAsyncDispatch).isFalse();
247+
}
248+
249+
@Test
250+
public void filterWhenObserveOncePerRequestDefaultThenTrue() {
251+
assertThat(this.filter.isObserveOncePerRequest()).isTrue();
252+
}
253+
254+
private void setIsAppliedTrue() {
255+
this.request.setAttribute(ALREADY_FILTERED_ATTRIBUTE_NAME, Boolean.TRUE);
256+
}
257+
135258
}

0 commit comments

Comments
 (0)