Skip to content

Commit aadb000

Browse files
committed
Polish PathPattern Support
1 parent 3d5bc54 commit aadb000

File tree

5 files changed

+95
-76
lines changed

5 files changed

+95
-76
lines changed

web/src/main/java/org/springframework/security/web/FilterChainProxy.java

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.util.Assert;
4747
import org.springframework.web.filter.DelegatingFilterProxy;
4848
import org.springframework.web.filter.GenericFilterBean;
49+
import org.springframework.web.filter.ServletRequestPathFilter;
4950

5051
/**
5152
* Delegates {@code Filter} requests to a list of Spring-managed filter beans. As of
@@ -162,6 +163,8 @@ public class FilterChainProxy extends GenericFilterBean {
162163

163164
private FilterChainDecorator filterChainDecorator = new VirtualFilterChainDecorator();
164165

166+
private Filter springWebFilter = new ServletRequestPathFilter();
167+
165168
public FilterChainProxy() {
166169
}
167170

@@ -210,27 +213,29 @@ private void doFilterInternal(ServletRequest request, ServletResponse response,
210213
throws IOException, ServletException {
211214
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
212215
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
213-
List<Filter> filters = getFilters(firewallRequest);
214-
if (filters == null || filters.isEmpty()) {
215-
if (logger.isTraceEnabled()) {
216-
logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
216+
this.springWebFilter.doFilter(firewallRequest, firewallResponse, (r, s) -> {
217+
List<Filter> filters = getFilters(firewallRequest);
218+
if (filters == null || filters.isEmpty()) {
219+
if (logger.isTraceEnabled()) {
220+
logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
221+
}
222+
firewallRequest.reset();
223+
this.filterChainDecorator.decorate(chain).doFilter(firewallRequest, firewallResponse);
224+
return;
217225
}
218-
firewallRequest.reset();
219-
this.filterChainDecorator.decorate(chain).doFilter(firewallRequest, firewallResponse);
220-
return;
221-
}
222-
if (logger.isDebugEnabled()) {
223-
logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
224-
}
225-
FilterChain reset = (req, res) -> {
226226
if (logger.isDebugEnabled()) {
227-
logger.debug(LogMessage.of(() -> "Secured " + requestLine(firewallRequest)));
227+
logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
228228
}
229-
// Deactivate path stripping as we exit the security filter chain
230-
firewallRequest.reset();
231-
chain.doFilter(req, res);
232-
};
233-
this.filterChainDecorator.decorate(reset, filters).doFilter(firewallRequest, firewallResponse);
229+
FilterChain reset = (req, res) -> {
230+
if (logger.isDebugEnabled()) {
231+
logger.debug(LogMessage.of(() -> "Secured " + requestLine(firewallRequest)));
232+
}
233+
// Deactivate path stripping as we exit the security filter chain
234+
firewallRequest.reset();
235+
chain.doFilter(req, res);
236+
};
237+
this.filterChainDecorator.decorate(reset, filters).doFilter(firewallRequest, firewallResponse);
238+
});
234239
}
235240

236241
/**
@@ -447,4 +452,23 @@ public FilterChain decorate(FilterChain original, List<Filter> filters) {
447452

448453
}
449454

455+
private static final class FirewallFilter implements Filter {
456+
457+
private final HttpFirewall firewall;
458+
459+
private FirewallFilter(HttpFirewall firewall) {
460+
this.firewall = firewall;
461+
}
462+
463+
@Override
464+
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
465+
throws IOException, ServletException {
466+
HttpServletRequest request = (HttpServletRequest) servletRequest;
467+
HttpServletResponse response = (HttpServletResponse) servletResponse;
468+
filterChain.doFilter(this.firewall.getFirewalledRequest(request),
469+
this.firewall.getFirewalledResponse(response));
470+
}
471+
472+
}
473+
450474
}

web/src/main/java/org/springframework/security/web/servlet/util/matcher/PathPatternRequestMatcher.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.springframework.lang.Nullable;
3333
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
3434
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
35-
import org.springframework.security.web.util.matcher.MethodPathRequestMatcherFactory;
3635
import org.springframework.security.web.util.matcher.RequestMatcher;
3736
import org.springframework.util.Assert;
3837
import org.springframework.web.util.ServletRequestPathUtils;
@@ -154,7 +153,7 @@ void setServletPath(RequestMatcher servletPath) {
154153
}
155154

156155
private RequestPath getRequestPath(HttpServletRequest request) {
157-
return ServletRequestPathUtils.parseAndCache(request);
156+
return ServletRequestPathUtils.getParsedRequestPath(request);
158157
}
159158

160159
/**
@@ -209,7 +208,7 @@ public String toString() {
209208
* ...
210209
* </code>
211210
*/
212-
public static final class Builder implements MethodPathRequestMatcherFactory {
211+
public static final class Builder {
213212

214213
private final PathPatternParser parser;
215214

@@ -234,6 +233,40 @@ public Builder servletPath(String servletPath) {
234233
return this;
235234
}
236235

236+
/**
237+
* Match requests having this path pattern.
238+
*
239+
* <p>
240+
* When the HTTP {@code method} is null, then the matcher does not consider the
241+
* HTTP method
242+
*
243+
* <p>
244+
* Path patterns always start with a slash and may contain placeholders. They can
245+
* also be followed by {@code /**} to signify all URIs under a given path.
246+
*
247+
* <p>
248+
* These must be specified relative to any servlet path prefix (meaning you should
249+
* exclude the context path and any servlet path prefix in stating your pattern).
250+
*
251+
* <p>
252+
* The following are valid patterns and their meaning
253+
* <ul>
254+
* <li>{@code /path} - match exactly and only `/path`</li>
255+
* <li>{@code /path/**} - match `/path` and any of its descendents</li>
256+
* <li>{@code /path/{value}/**} - match `/path/subdirectory` and any of its
257+
* descendents, capturing the value of the subdirectory in
258+
* {@link RequestAuthorizationContext#getVariables()}</li>
259+
* </ul>
260+
*
261+
* <p>
262+
* A more comprehensive list can be found at {@link PathPattern}.
263+
* @param path the path pattern to match
264+
* @return the {@link Builder} for more configuration
265+
*/
266+
public PathPatternRequestMatcher matcher(String path) {
267+
return matcher(null, path);
268+
}
269+
237270
/**
238271
* Match requests having this {@link HttpMethod} and path pattern.
239272
*

web/src/main/java/org/springframework/security/web/util/matcher/MethodPathRequestMatcherFactory.java

Lines changed: 0 additions & 49 deletions
This file was deleted.

web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.springframework.security.web.firewall.HttpFirewall;
4949
import org.springframework.security.web.firewall.RequestRejectedException;
5050
import org.springframework.security.web.firewall.RequestRejectedHandler;
51+
import org.springframework.security.web.servlet.TestMockHttpServletMappings;
5152
import org.springframework.security.web.util.matcher.RequestMatcher;
5253

5354
import static org.assertj.core.api.Assertions.assertThat;
@@ -166,6 +167,7 @@ public void wrapperIsResetWhenNoMatchingFilters() throws Exception {
166167
FirewalledRequest fwr = mock(FirewalledRequest.class);
167168
given(fwr.getRequestURI()).willReturn("/");
168169
given(fwr.getContextPath()).willReturn("");
170+
given(fwr.getHttpServletMapping()).willReturn(TestMockHttpServletMappings.defaultMapping());
169171
this.fcp.setFirewall(fw);
170172
given(fw.getFirewalledRequest(this.request)).willReturn(fwr);
171173
given(this.matcher.matches(any(HttpServletRequest.class))).willReturn(false);
@@ -183,9 +185,11 @@ public void bothWrappersAreResetWithNestedFcps() throws Exception {
183185
FirewalledRequest firstFwr = mock(FirewalledRequest.class, "firstFwr");
184186
given(firstFwr.getRequestURI()).willReturn("/");
185187
given(firstFwr.getContextPath()).willReturn("");
188+
given(firstFwr.getHttpServletMapping()).willReturn(TestMockHttpServletMappings.defaultMapping());
186189
FirewalledRequest fwr = mock(FirewalledRequest.class, "fwr");
187190
given(fwr.getRequestURI()).willReturn("/");
188191
given(fwr.getContextPath()).willReturn("");
192+
given(fwr.getHttpServletMapping()).willReturn(TestMockHttpServletMappings.defaultMapping());
189193
given(fw.getFirewalledRequest(this.request)).willReturn(firstFwr);
190194
given(fw.getFirewalledRequest(firstFwr)).willReturn(fwr);
191195
given(fwr.getRequest()).willReturn(firstFwr);

web/src/test/java/org/springframework/security/web/servlet/util/matcher/PathPatternRequestMatcherTests.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,25 @@ void matcherWhenServletPathThenMatchesOnlyServletPath() {
9090
PathPatternRequestMatcher.Builder servlet = PathPatternRequestMatcher.servletPath("/servlet/path");
9191
RequestMatcher matcher = servlet.matcher(HttpMethod.GET, "/endpoint");
9292
ServletContext servletContext = servletContext("/servlet/path");
93-
assertThat(matcher
94-
.matches(get("/servlet/path/endpoint").servletPath("/servlet/path").buildRequest(servletContext))).isTrue();
95-
assertThat(matcher.matches(get("/endpoint").servletPath("/endpoint").buildRequest(servletContext))).isFalse();
93+
MockHttpServletRequest mock = get("/servlet/path/endpoint").servletPath("/servlet/path")
94+
.buildRequest(servletContext);
95+
ServletRequestPathUtils.parseAndCache(mock);
96+
assertThat(matcher.matches(mock)).isTrue();
97+
mock = get("/endpoint").servletPath("/endpoint").buildRequest(servletContext);
98+
ServletRequestPathUtils.parseAndCache(mock);
99+
assertThat(matcher.matches(mock)).isFalse();
96100
}
97101

98102
@Test
99103
void matcherWhenRequestPathThenIgnoresServletPath() {
100104
PathPatternRequestMatcher.Builder request = PathPatternRequestMatcher.path();
101105
RequestMatcher matcher = request.matcher(HttpMethod.GET, "/endpoint");
102-
assertThat(matcher.matches(get("/servlet/path/endpoint").servletPath("/servlet/path").buildRequest(null)))
103-
.isTrue();
104-
assertThat(matcher.matches(get("/endpoint").servletPath("/endpoint").buildRequest(null))).isTrue();
106+
MockHttpServletRequest mock = get("/servlet/path/endpoint").servletPath("/servlet/path").buildRequest(null);
107+
ServletRequestPathUtils.parseAndCache(mock);
108+
assertThat(matcher.matches(mock)).isTrue();
109+
mock = get("/endpoint").servletPath("/endpoint").buildRequest(null);
110+
ServletRequestPathUtils.parseAndCache(mock);
111+
assertThat(matcher.matches(mock)).isTrue();
105112
}
106113

107114
@Test

0 commit comments

Comments
 (0)