|
16 | 16 |
|
17 | 17 | package org.springframework.security.web.util.matcher;
|
18 | 18 |
|
19 |
| -import java.nio.charset.StandardCharsets; |
20 |
| -import java.util.Collection; |
21 |
| -import java.util.LinkedHashMap; |
22 | 19 | import java.util.List;
|
23 |
| -import java.util.Map; |
24 |
| -import java.util.Objects; |
25 |
| -import java.util.concurrent.atomic.AtomicReference; |
26 |
| - |
27 |
| -import jakarta.servlet.DispatcherType; |
28 |
| -import jakarta.servlet.RequestDispatcher; |
29 |
| -import jakarta.servlet.ServletContext; |
30 |
| -import jakarta.servlet.ServletRegistration; |
31 |
| -import jakarta.servlet.http.HttpServletMapping; |
32 |
| -import jakarta.servlet.http.HttpServletRequest; |
33 |
| -import jakarta.servlet.http.MappingMatch; |
34 |
| - |
35 |
| -import org.springframework.http.HttpMethod; |
36 |
| -import org.springframework.lang.Nullable; |
37 |
| -import org.springframework.security.web.access.intercept.RequestAuthorizationContext; |
38 |
| -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; |
39 |
| -import org.springframework.util.Assert; |
40 |
| -import org.springframework.util.ObjectUtils; |
41 |
| -import org.springframework.web.util.UriUtils; |
42 |
| -import org.springframework.web.util.WebUtils; |
43 |
| -import org.springframework.web.util.pattern.PathPattern; |
44 |
| -import org.springframework.web.util.pattern.PathPatternParser; |
45 | 20 |
|
46 | 21 | /**
|
47 | 22 | * A factory class to create {@link RequestMatcher} instances.
|
@@ -85,261 +60,7 @@ public static RequestMatcher not(RequestMatcher matcher) {
|
85 | 60 | return (request) -> !matcher.matches(request);
|
86 | 61 | }
|
87 | 62 |
|
88 |
| - /** |
89 |
| - * Create {@link RequestMatcher}s whose URIs do not have a servlet path prefix |
90 |
| - * <p> |
91 |
| - * When there is no context path, then these URIs are effectively absolute. |
92 |
| - * @return a {@link Builder} that treats URIs as relative to the context path, if any |
93 |
| - * @since 6.5 |
94 |
| - */ |
95 |
| - public static Builder request() { |
96 |
| - return new Builder(); |
97 |
| - } |
98 |
| - |
99 |
| - /** |
100 |
| - * Create {@link RequestMatcher}s whose URIs are relative to the given |
101 |
| - * {@code servletPath} prefix. |
102 |
| - * |
103 |
| - * <p> |
104 |
| - * The {@code servletPath} must correlate to a value that would match the result of |
105 |
| - * {@link HttpServletRequest#getServletPath()} and its corresponding servlet. |
106 |
| - * |
107 |
| - * <p> |
108 |
| - * That is, if you have a servlet mapping of {@code /path/*}, then |
109 |
| - * {@link HttpServletRequest#getServletPath()} would return {@code /path} and so |
110 |
| - * {@code /path} is what is specified here. |
111 |
| - * |
112 |
| - * Specify the path here without the trailing {@code /*}. |
113 |
| - * @return a {@link Builder} that treats URIs as relative to the given |
114 |
| - * {@code servletPath} |
115 |
| - * @since 6.5 |
116 |
| - */ |
117 |
| - public static Builder servletPath(String servletPath) { |
118 |
| - Assert.notNull(servletPath, "servletPath cannot be null"); |
119 |
| - Assert.isTrue(servletPath.startsWith("/"), "servletPath must start with '/'"); |
120 |
| - Assert.isTrue(!servletPath.endsWith("/"), "servletPath must not end with a slash"); |
121 |
| - Assert.isTrue(!servletPath.contains("*"), "servletPath must not contain a star"); |
122 |
| - return new Builder(servletPath); |
123 |
| - } |
124 |
| - |
125 | 63 | private RequestMatchers() {
|
126 | 64 | }
|
127 | 65 |
|
128 |
| - /** |
129 |
| - * A builder for specifying various elements of a request for the purpose of creating |
130 |
| - * a {@link RequestMatcher}. |
131 |
| - * |
132 |
| - * <p> |
133 |
| - * For example, if Spring MVC is deployed to `/mvc` and another servlet to `/other`, |
134 |
| - * then you can do: |
135 |
| - * </p> |
136 |
| - * |
137 |
| - * <code> |
138 |
| - * http |
139 |
| - * .authorizeHttpRequests((authorize) -> authorize |
140 |
| - * .requestMatchers(servlet("/mvc").uris("/user/**")).hasAuthority("user") |
141 |
| - * .requestMatchers(servlet("/other").uris("/admin/**")).hasAuthority("admin") |
142 |
| - * ) |
143 |
| - * ... |
144 |
| - * </code> |
145 |
| - * |
146 |
| - * @author Josh Cummings |
147 |
| - * @since 6.5 |
148 |
| - */ |
149 |
| - public static final class Builder { |
150 |
| - |
151 |
| - private final RequestMatcher servletPath; |
152 |
| - |
153 |
| - private final RequestMatcher methods; |
154 |
| - |
155 |
| - private final RequestMatcher uris; |
156 |
| - |
157 |
| - private final RequestMatcher dispatcherTypes; |
158 |
| - |
159 |
| - private Builder() { |
160 |
| - this(AnyRequestMatcher.INSTANCE, AnyRequestMatcher.INSTANCE, AnyRequestMatcher.INSTANCE, |
161 |
| - AnyRequestMatcher.INSTANCE); |
162 |
| - } |
163 |
| - |
164 |
| - private Builder(String servletPath) { |
165 |
| - this(new ServletPathRequestMatcher(servletPath), AnyRequestMatcher.INSTANCE, AnyRequestMatcher.INSTANCE, |
166 |
| - AnyRequestMatcher.INSTANCE); |
167 |
| - } |
168 |
| - |
169 |
| - private Builder(RequestMatcher servletPath, RequestMatcher methods, RequestMatcher uris, |
170 |
| - RequestMatcher dispatcherTypes) { |
171 |
| - this.servletPath = servletPath; |
172 |
| - this.methods = methods; |
173 |
| - this.uris = uris; |
174 |
| - this.dispatcherTypes = dispatcherTypes; |
175 |
| - } |
176 |
| - |
177 |
| - /** |
178 |
| - * Match requests with any of these methods |
179 |
| - * @param methods the {@link HttpMethod} to match |
180 |
| - * @return the {@link Builder} for more configuration |
181 |
| - */ |
182 |
| - public Builder methods(HttpMethod... methods) { |
183 |
| - RequestMatcher[] matchers = new RequestMatcher[methods.length]; |
184 |
| - for (int i = 0; i < methods.length; i++) { |
185 |
| - matchers[i] = new HttpMethodRequestMatcher(methods[i]); |
186 |
| - } |
187 |
| - return new Builder(this.servletPath, anyOf(matchers), this.uris, this.dispatcherTypes); |
188 |
| - } |
189 |
| - |
190 |
| - /** |
191 |
| - * Match requests with any of these path patterns |
192 |
| - * |
193 |
| - * <p> |
194 |
| - * Path patterns always start with a slash and may contain placeholders. They can |
195 |
| - * also be followed by {@code /**} to signify all URIs under a given path. |
196 |
| - * |
197 |
| - * <p> |
198 |
| - * These must be specified relative to any servlet path prefix (meaning you should |
199 |
| - * exclude the context path and any servlet path prefix in stating your pattern). |
200 |
| - * |
201 |
| - * <p> |
202 |
| - * The following are valid patterns and their meaning |
203 |
| - * <ul> |
204 |
| - * <li>{@code /path} - match exactly and only `/path`</li> |
205 |
| - * <li>{@code /path/**} - match `/path` and any of its descendents</li> |
206 |
| - * <li>{@code /path/{value}/**} - match `/path/subdirectory` and any of its |
207 |
| - * descendents, capturing the value of the subdirectory in |
208 |
| - * {@link RequestAuthorizationContext#getVariables()}</li> |
209 |
| - * </ul> |
210 |
| - * |
211 |
| - * <p> |
212 |
| - * A more comprehensive list can be found at {@link PathPattern}. |
213 |
| - * @param pathPatterns the path patterns to match |
214 |
| - * @return the {@link Builder} for more configuration |
215 |
| - */ |
216 |
| - public Builder pathPatterns(String... pathPatterns) { |
217 |
| - RequestMatcher[] matchers = new RequestMatcher[pathPatterns.length]; |
218 |
| - for (int i = 0; i < pathPatterns.length; i++) { |
219 |
| - Assert.isTrue(pathPatterns[i].startsWith("/"), "path patterns must start with /"); |
220 |
| - PathPatternParser parser = PathPatternParser.defaultInstance; |
221 |
| - matchers[i] = new PathPatternRequestMatcher(parser.parse(pathPatterns[i])); |
222 |
| - } |
223 |
| - return new Builder(this.servletPath, this.methods, anyOf(matchers), this.dispatcherTypes); |
224 |
| - } |
225 |
| - |
226 |
| - /** |
227 |
| - * Match requests with any of these {@link PathPattern}s |
228 |
| - * |
229 |
| - * <p> |
230 |
| - * Use this when you have a non-default {@link PathPatternParser} |
231 |
| - * @param pathPatterns the URIs to match |
232 |
| - * @return the {@link Builder} for more configuration |
233 |
| - */ |
234 |
| - public Builder pathPatterns(PathPattern... pathPatterns) { |
235 |
| - RequestMatcher[] matchers = new RequestMatcher[pathPatterns.length]; |
236 |
| - for (int i = 0; i < pathPatterns.length; i++) { |
237 |
| - matchers[i] = new PathPatternRequestMatcher(pathPatterns[i]); |
238 |
| - } |
239 |
| - return new Builder(this.servletPath, this.methods, anyOf(matchers), this.dispatcherTypes); |
240 |
| - } |
241 |
| - |
242 |
| - /** |
243 |
| - * Match requests with any of these dispatcherTypes |
244 |
| - * @param dispatcherTypes the {@link DispatcherType}s to match |
245 |
| - * @return the {@link Builder} for more configuration |
246 |
| - */ |
247 |
| - public Builder dispatcherTypes(DispatcherType... dispatcherTypes) { |
248 |
| - RequestMatcher[] matchers = new RequestMatcher[dispatcherTypes.length]; |
249 |
| - for (int i = 0; i < dispatcherTypes.length; i++) { |
250 |
| - matchers[i] = new DispatcherTypeRequestMatcher(dispatcherTypes[i]); |
251 |
| - } |
252 |
| - return new Builder(this.servletPath, this.methods, this.uris, anyOf(matchers)); |
253 |
| - } |
254 |
| - |
255 |
| - /** |
256 |
| - * Create the {@link RequestMatcher} |
257 |
| - * @return the composite {@link RequestMatcher} |
258 |
| - */ |
259 |
| - public RequestMatcher matcher() { |
260 |
| - return allOf(this.servletPath, this.methods, this.uris, this.dispatcherTypes); |
261 |
| - } |
262 |
| - |
263 |
| - } |
264 |
| - |
265 |
| - private record HttpMethodRequestMatcher(HttpMethod method) implements RequestMatcher { |
266 |
| - |
267 |
| - @Override |
268 |
| - public boolean matches(HttpServletRequest request) { |
269 |
| - return this.method.name().equals(request.getMethod()); |
270 |
| - } |
271 |
| - |
272 |
| - @Override |
273 |
| - public String toString() { |
274 |
| - return "HttpMethod [" + this.method + "]"; |
275 |
| - } |
276 |
| - |
277 |
| - } |
278 |
| - |
279 |
| - private static final class ServletPathRequestMatcher implements RequestMatcher { |
280 |
| - |
281 |
| - private final String path; |
282 |
| - |
283 |
| - private final AtomicReference<Boolean> servletExists = new AtomicReference(); |
284 |
| - |
285 |
| - ServletPathRequestMatcher(String servletPath) { |
286 |
| - this.path = servletPath; |
287 |
| - } |
288 |
| - |
289 |
| - @Override |
290 |
| - public boolean matches(HttpServletRequest request) { |
291 |
| - Assert.isTrue(servletExists(request), () -> this.path + "/* does not exist in your servlet registration " |
292 |
| - + registrationMappings(request)); |
293 |
| - return Objects.equals(this.path, getServletPathPrefix(request)); |
294 |
| - } |
295 |
| - |
296 |
| - private boolean servletExists(HttpServletRequest request) { |
297 |
| - return this.servletExists.updateAndGet((value) -> { |
298 |
| - if (value != null) { |
299 |
| - return value; |
300 |
| - } |
301 |
| - if (request.getAttribute("org.springframework.test.web.servlet.MockMvc.MVC_RESULT_ATTRIBUTE") != null) { |
302 |
| - return true; |
303 |
| - } |
304 |
| - for (ServletRegistration registration : request.getServletContext() |
305 |
| - .getServletRegistrations() |
306 |
| - .values()) { |
307 |
| - if (registration.getMappings().contains(this.path + "/*")) { |
308 |
| - return true; |
309 |
| - } |
310 |
| - } |
311 |
| - return false; |
312 |
| - }); |
313 |
| - } |
314 |
| - |
315 |
| - private Map<String, Collection<String>> registrationMappings(HttpServletRequest request) { |
316 |
| - Map<String, Collection<String>> map = new LinkedHashMap<>(); |
317 |
| - ServletContext servletContext = request.getServletContext(); |
318 |
| - for (ServletRegistration registration : servletContext.getServletRegistrations().values()) { |
319 |
| - map.put(registration.getName(), registration.getMappings()); |
320 |
| - } |
321 |
| - return map; |
322 |
| - } |
323 |
| - |
324 |
| - @Nullable |
325 |
| - private static String getServletPathPrefix(HttpServletRequest request) { |
326 |
| - HttpServletMapping mapping = (HttpServletMapping) request.getAttribute(RequestDispatcher.INCLUDE_MAPPING); |
327 |
| - mapping = (mapping != null) ? mapping : request.getHttpServletMapping(); |
328 |
| - if (ObjectUtils.nullSafeEquals(mapping.getMappingMatch(), MappingMatch.PATH)) { |
329 |
| - String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE); |
330 |
| - servletPath = (servletPath != null) ? servletPath : request.getServletPath(); |
331 |
| - servletPath = servletPath.endsWith("/") ? servletPath.substring(0, servletPath.length() - 1) |
332 |
| - : servletPath; |
333 |
| - return UriUtils.encodePath(servletPath, StandardCharsets.UTF_8); |
334 |
| - } |
335 |
| - return null; |
336 |
| - } |
337 |
| - |
338 |
| - @Override |
339 |
| - public String toString() { |
340 |
| - return "ServletPath [" + this.path + "]"; |
341 |
| - } |
342 |
| - |
343 |
| - } |
344 |
| - |
345 | 66 | }
|
0 commit comments