Skip to content

Commit c53bf2b

Browse files
committed
PathPatternRequestParser Retains Servlet Path
Issue gh-16765
1 parent 1966ff3 commit c53bf2b

File tree

2 files changed

+35
-92
lines changed

2 files changed

+35
-92
lines changed

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

Lines changed: 17 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,8 @@
1616

1717
package org.springframework.security.web.servlet.util.matcher;
1818

19-
import java.util.Collection;
20-
import java.util.LinkedHashMap;
21-
import java.util.Map;
2219
import java.util.Objects;
23-
import java.util.concurrent.atomic.AtomicReference;
2420

25-
import jakarta.servlet.ServletContext;
26-
import jakarta.servlet.ServletRegistration;
2721
import jakarta.servlet.http.HttpServletRequest;
2822

2923
import org.springframework.http.HttpMethod;
@@ -40,11 +34,12 @@
4034

4135
/**
4236
* A {@link RequestMatcher} that uses {@link PathPattern}s to match against each
43-
* {@link HttpServletRequest}. The provided path should be relative to the servlet (that
44-
* is, it should exclude any context or servlet path).
37+
* {@link HttpServletRequest}. The provided path should be relative to the context path
38+
* (that is, it should exclude any context path).
4539
*
4640
* <p>
47-
* To also match the servlet, please see {@link PathPatternRequestMatcher#servletPath}
41+
* You can provide the servlet path in {@link PathPatternRequestMatcher#servletPath} and
42+
* reuse for multiple matchers.
4843
*
4944
* <p>
5045
* Note that the {@link org.springframework.web.servlet.HandlerMapping} that contains the
@@ -113,7 +108,7 @@ public MatchResult matcher(HttpServletRequest request) {
113108
if (!this.method.matches(request)) {
114109
return MatchResult.notMatch();
115110
}
116-
PathContainer path = getRequestPath(request).pathWithinApplication();
111+
PathContainer path = getPathContainer(request);
117112
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(path);
118113
return (info != null) ? MatchResult.match(info.getUriVariables()) : MatchResult.notMatch();
119114
}
@@ -122,11 +117,7 @@ void setMethod(RequestMatcher method) {
122117
this.method = method;
123118
}
124119

125-
void setServletPath(RequestMatcher servletPath) {
126-
this.servletPath = servletPath;
127-
}
128-
129-
private RequestPath getRequestPath(HttpServletRequest request) {
120+
private PathContainer getPathContainer(HttpServletRequest request) {
130121
RequestPath path;
131122
if (ServletRequestPathUtils.hasParsedRequestPath(request)) {
132123
path = ServletRequestPathUtils.getParsedRequestPath(request);
@@ -135,7 +126,8 @@ private RequestPath getRequestPath(HttpServletRequest request) {
135126
path = ServletRequestPathUtils.parseAndCache(request);
136127
ServletRequestPathUtils.clearParsedRequestPath(request);
137128
}
138-
return path;
129+
PathContainer contextPath = path.contextPath();
130+
return path.subPath(contextPath.elements().size());
139131
}
140132

141133
/**
@@ -166,9 +158,6 @@ public String toString() {
166158
if (this.method instanceof HttpMethodRequestMatcher m) {
167159
request.append(m.method.name()).append(' ');
168160
}
169-
if (this.servletPath instanceof ServletPathRequestMatcher s) {
170-
request.append(s.path);
171-
}
172161
return "PathPattern [" + request + this.pattern + "]";
173162
}
174163

@@ -194,17 +183,17 @@ public static final class Builder {
194183

195184
private final PathPatternParser parser;
196185

197-
private final RequestMatcher servletPath;
186+
private final String servletPath;
198187

199188
Builder() {
200189
this(PathPatternParser.defaultInstance);
201190
}
202191

203192
Builder(PathPatternParser parser) {
204-
this(parser, AnyRequestMatcher.INSTANCE);
193+
this(parser, "");
205194
}
206195

207-
Builder(PathPatternParser parser, RequestMatcher servletPath) {
196+
Builder(PathPatternParser parser, String servletPath) {
208197
this.parser = parser;
209198
this.servletPath = servletPath;
210199
}
@@ -215,7 +204,11 @@ public static final class Builder {
215204
* @return the {@link Builder} for more configuration
216205
*/
217206
public Builder servletPath(String servletPath) {
218-
return new Builder(this.parser, new ServletPathRequestMatcher(servletPath));
207+
Assert.notNull(servletPath, "servletPath cannot be null");
208+
Assert.isTrue(servletPath.startsWith("/"), "servletPath must start with '/'");
209+
Assert.isTrue(!servletPath.endsWith("/"), "servletPath must not end with a slash");
210+
Assert.isTrue(!servletPath.contains("*"), "servletPath must not contain a star");
211+
return new Builder(this.parser, servletPath);
219212
}
220213

221214
/**
@@ -286,14 +279,11 @@ public PathPatternRequestMatcher matcher(String path) {
286279
public PathPatternRequestMatcher matcher(@Nullable HttpMethod method, String path) {
287280
Assert.notNull(path, "pattern cannot be null");
288281
Assert.isTrue(path.startsWith("/"), "pattern must start with a /");
289-
PathPattern pathPattern = this.parser.parse(path);
282+
PathPattern pathPattern = this.parser.parse(this.servletPath + path);
290283
PathPatternRequestMatcher requestMatcher = new PathPatternRequestMatcher(pathPattern);
291284
if (method != null) {
292285
requestMatcher.setMethod(new HttpMethodRequestMatcher(method));
293286
}
294-
if (this.servletPath != AnyRequestMatcher.INSTANCE) {
295-
requestMatcher.setServletPath(this.servletPath);
296-
}
297287
return requestMatcher;
298288
}
299289

@@ -319,60 +309,4 @@ public String toString() {
319309

320310
}
321311

322-
private static final class ServletPathRequestMatcher implements RequestMatcher {
323-
324-
private final String path;
325-
326-
private final AtomicReference<Boolean> servletExists = new AtomicReference<>();
327-
328-
ServletPathRequestMatcher(String servletPath) {
329-
Assert.notNull(servletPath, "servletPath cannot be null");
330-
Assert.isTrue(servletPath.startsWith("/"), "servletPath must start with '/'");
331-
Assert.isTrue(!servletPath.endsWith("/"), "servletPath must not end with a slash");
332-
Assert.isTrue(!servletPath.contains("*"), "servletPath must not contain a star");
333-
this.path = servletPath;
334-
}
335-
336-
@Override
337-
public boolean matches(HttpServletRequest request) {
338-
Assert.isTrue(servletExists(request), () -> this.path + "/* does not exist in your servlet registration "
339-
+ registrationMappings(request));
340-
return Objects.equals(this.path, ServletRequestPathUtils.getServletPathPrefix(request));
341-
}
342-
343-
private boolean servletExists(HttpServletRequest request) {
344-
return this.servletExists.updateAndGet((value) -> {
345-
if (value != null) {
346-
return value;
347-
}
348-
if (request.getAttribute("org.springframework.test.web.servlet.MockMvc.MVC_RESULT_ATTRIBUTE") != null) {
349-
return true;
350-
}
351-
for (ServletRegistration registration : request.getServletContext()
352-
.getServletRegistrations()
353-
.values()) {
354-
if (registration.getMappings().contains(this.path + "/*")) {
355-
return true;
356-
}
357-
}
358-
return false;
359-
});
360-
}
361-
362-
private Map<String, Collection<String>> registrationMappings(HttpServletRequest request) {
363-
Map<String, Collection<String>> map = new LinkedHashMap<>();
364-
ServletContext servletContext = request.getServletContext();
365-
for (ServletRegistration registration : servletContext.getServletRegistrations().values()) {
366-
map.put(registration.getName(), registration.getMappings());
367-
}
368-
return map;
369-
}
370-
371-
@Override
372-
public String toString() {
373-
return "ServletPath [" + this.path + "]";
374-
}
375-
376-
}
377-
378312
}

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

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ void matcherWhenPatternContainsPlaceholdersThenMatchResult() {
4949
}
5050

5151
@Test
52-
void matcherWhenOnlyPathInfoMatchesThenMatches() {
52+
void matcherWhenOnlyPathInfoMatchesThenNoMatch() {
5353
RequestMatcher matcher = PathPatternRequestMatcher.withDefaults().matcher("/uri");
54-
assertThat(matcher.matches(request("GET", "/mvc/uri", "/mvc"))).isTrue();
54+
assertThat(matcher.matches(request("GET", "/mvc/uri", "/mvc"))).isFalse();
5555
}
5656

5757
@Test
58-
void matcherWhenUriContainsServletPathThenNoMatch() {
58+
void matcherWhenUriContainsServletPathThenMatch() {
5959
RequestMatcher matcher = PathPatternRequestMatcher.withDefaults().matcher("/mvc/uri");
60-
assertThat(matcher.matches(request("GET", "/mvc/uri", "/mvc"))).isFalse();
60+
assertThat(matcher.matches(request("GET", "/mvc/uri", "/mvc"))).isTrue();
6161
}
6262

6363
@Test
@@ -101,24 +101,33 @@ void matcherWhenServletPathThenMatchesOnlyServletPath() {
101101
}
102102

103103
@Test
104-
void matcherWhenRequestPathThenIgnoresServletPath() {
104+
void matcherWhenRequestPathThenRequiresServletPath() {
105105
PathPatternRequestMatcher.Builder request = PathPatternRequestMatcher.withDefaults();
106106
RequestMatcher matcher = request.matcher(HttpMethod.GET, "/endpoint");
107107
MockHttpServletRequest mock = get("/servlet/path/endpoint").servletPath("/servlet/path").buildRequest(null);
108108
ServletRequestPathUtils.parseAndCache(mock);
109-
assertThat(matcher.matches(mock)).isTrue();
109+
assertThat(matcher.matches(mock)).isFalse();
110110
mock = get("/endpoint").servletPath("/endpoint").buildRequest(null);
111111
ServletRequestPathUtils.parseAndCache(mock);
112112
assertThat(matcher.matches(mock)).isTrue();
113113
}
114114

115115
@Test
116-
void matcherWhenServletPathThenRequiresServletPathToExist() {
116+
void matcherWhenMultiServletPathThenMatches() {
117+
PathPatternRequestMatcher.Builder servlet = PathPatternRequestMatcher.withDefaults()
118+
.servletPath("/servlet/path");
119+
RequestMatcher matcher = servlet.matcher(HttpMethod.GET, "/endpoint");
120+
MockHttpServletRequest mock = get("/servlet/path/endpoint").servletPath("/servlet/path").buildRequest(null);
121+
assertThat(matcher.matches(mock)).isTrue();
122+
}
123+
124+
@Test
125+
void matcherWhenMultiContextPathThenMatches() {
117126
PathPatternRequestMatcher.Builder servlet = PathPatternRequestMatcher.withDefaults()
118127
.servletPath("/servlet/path");
119128
RequestMatcher matcher = servlet.matcher(HttpMethod.GET, "/endpoint");
120-
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(
121-
() -> matcher.matches(get("/servlet/path/endpoint").servletPath("/servlet/path").buildRequest(null)));
129+
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> matcher.matches(
130+
get("/servlet/path/endpoint").servletPath("/servlet/path").contextPath("/app").buildRequest(null)));
122131
}
123132

124133
@Test

0 commit comments

Comments
 (0)