Skip to content

Commit 6492706

Browse files
committed
Merge pull request #29596 from onobc
* pr/29596: Polish 'Add ability to match Endpoint requests by HTTP method' Add ability to match Endpoint requests by HTTP method Closes gh-29596
2 parents 2b5d8a4 + 9576293 commit 6492706

File tree

12 files changed

+531
-57
lines changed

12 files changed

+531
-57
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 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.
@@ -40,6 +40,7 @@
4040
import org.springframework.context.ApplicationContext;
4141
import org.springframework.core.annotation.MergedAnnotation;
4242
import org.springframework.core.annotation.MergedAnnotations;
43+
import org.springframework.http.HttpMethod;
4344
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
4445
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
4546
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
@@ -56,6 +57,7 @@
5657
*
5758
* @author Madhura Bhave
5859
* @author Phillip Webb
60+
* @author Chris Bono
5961
* @since 2.0.0
6062
*/
6163
public final class EndpointRequest {
@@ -180,12 +182,14 @@ private ServerWebExchangeMatcher createDelegate(Supplier<C> context) {
180182

181183
protected abstract ServerWebExchangeMatcher createDelegate(C context);
182184

183-
protected final List<ServerWebExchangeMatcher> getDelegateMatchers(Set<String> paths) {
184-
return paths.stream().map(this::getDelegateMatcher).collect(Collectors.toCollection(ArrayList::new));
185+
protected final List<ServerWebExchangeMatcher> getDelegateMatchers(Set<String> paths, HttpMethod httpMethod) {
186+
return paths.stream()
187+
.map((path) -> getDelegateMatcher(path, httpMethod))
188+
.collect(Collectors.toCollection(ArrayList::new));
185189
}
186190

187-
private PathPatternParserServerWebExchangeMatcher getDelegateMatcher(String path) {
188-
return new PathPatternParserServerWebExchangeMatcher(path + "/**");
191+
private PathPatternParserServerWebExchangeMatcher getDelegateMatcher(String path, HttpMethod httpMethod) {
192+
return new PathPatternParserServerWebExchangeMatcher(path + "/**", httpMethod);
189193
}
190194

191195
@Override
@@ -258,39 +262,53 @@ public static final class EndpointServerWebExchangeMatcher extends AbstractWebEx
258262

259263
private final boolean includeLinks;
260264

265+
private final HttpMethod httpMethod;
266+
261267
private EndpointServerWebExchangeMatcher(boolean includeLinks) {
262-
this(Collections.emptyList(), Collections.emptyList(), includeLinks);
268+
this(Collections.emptyList(), Collections.emptyList(), includeLinks, null);
263269
}
264270

265271
private EndpointServerWebExchangeMatcher(Class<?>[] endpoints, boolean includeLinks) {
266-
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks);
272+
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks, null);
267273
}
268274

269275
private EndpointServerWebExchangeMatcher(String[] endpoints, boolean includeLinks) {
270-
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks);
276+
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks, null);
271277
}
272278

273-
private EndpointServerWebExchangeMatcher(List<Object> includes, List<Object> excludes, boolean includeLinks) {
279+
private EndpointServerWebExchangeMatcher(List<Object> includes, List<Object> excludes, boolean includeLinks,
280+
HttpMethod httpMethod) {
274281
super(PathMappedEndpoints.class);
275282
this.includes = includes;
276283
this.excludes = excludes;
277284
this.includeLinks = includeLinks;
285+
this.httpMethod = httpMethod;
278286
}
279287

280288
public EndpointServerWebExchangeMatcher excluding(Class<?>... endpoints) {
281289
List<Object> excludes = new ArrayList<>(this.excludes);
282290
excludes.addAll(Arrays.asList((Object[]) endpoints));
283-
return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks);
291+
return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks, null);
284292
}
285293

286294
public EndpointServerWebExchangeMatcher excluding(String... endpoints) {
287295
List<Object> excludes = new ArrayList<>(this.excludes);
288296
excludes.addAll(Arrays.asList((Object[]) endpoints));
289-
return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks);
297+
return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks, null);
290298
}
291299

292300
public EndpointServerWebExchangeMatcher excludingLinks() {
293-
return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, false);
301+
return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, false, null);
302+
}
303+
304+
/**
305+
* Restricts the matcher to only consider requests with a particular http method.
306+
* @param httpMethod the http method to include
307+
* @return a copy of the matcher further restricted to only match requests with
308+
* the specified http method
309+
*/
310+
public EndpointServerWebExchangeMatcher withHttpMethod(HttpMethod httpMethod) {
311+
return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, this.includeLinks, httpMethod);
294312
}
295313

296314
@Override
@@ -301,7 +319,7 @@ protected ServerWebExchangeMatcher createDelegate(PathMappedEndpoints endpoints)
301319
}
302320
streamPaths(this.includes, endpoints).forEach(paths::add);
303321
streamPaths(this.excludes, endpoints).forEach(paths::remove);
304-
List<ServerWebExchangeMatcher> delegateMatchers = getDelegateMatchers(paths);
322+
List<ServerWebExchangeMatcher> delegateMatchers = getDelegateMatchers(paths, this.httpMethod);
305323
if (this.includeLinks && StringUtils.hasText(endpoints.getBasePath())) {
306324
delegateMatchers.add(new LinksServerWebExchangeMatcher());
307325
}
@@ -357,22 +375,37 @@ public static class AdditionalPathsEndpointServerWebExchangeMatcher
357375

358376
private final List<Object> endpoints;
359377

378+
private final HttpMethod httpMethod;
379+
360380
AdditionalPathsEndpointServerWebExchangeMatcher(WebServerNamespace webServerNamespace, String... endpoints) {
361-
this(webServerNamespace, Arrays.asList((Object[]) endpoints));
381+
this(webServerNamespace, Arrays.asList((Object[]) endpoints), null);
362382
}
363383

364384
AdditionalPathsEndpointServerWebExchangeMatcher(WebServerNamespace webServerNamespace, Class<?>... endpoints) {
365-
this(webServerNamespace, Arrays.asList((Object[]) endpoints));
385+
this(webServerNamespace, Arrays.asList((Object[]) endpoints), null);
366386
}
367387

368388
private AdditionalPathsEndpointServerWebExchangeMatcher(WebServerNamespace webServerNamespace,
369-
List<Object> endpoints) {
389+
List<Object> endpoints, HttpMethod httpMethod) {
370390
super(PathMappedEndpoints.class);
371391
Assert.notNull(webServerNamespace, "'webServerNamespace' must not be null");
372392
Assert.notNull(endpoints, "'endpoints' must not be null");
373393
Assert.notEmpty(endpoints, "'endpoints' must not be empty");
374394
this.webServerNamespace = webServerNamespace;
375395
this.endpoints = endpoints;
396+
this.httpMethod = httpMethod;
397+
}
398+
399+
/**
400+
* Restricts the matcher to only consider requests with a particular HTTP method.
401+
* @param httpMethod the HTTP method to include
402+
* @return a copy of the matcher further restricted to only match requests with
403+
* the specified HTTP method
404+
* @since 3.5.0
405+
*/
406+
public AdditionalPathsEndpointServerWebExchangeMatcher withHttpMethod(HttpMethod httpMethod) {
407+
return new AdditionalPathsEndpointServerWebExchangeMatcher(this.webServerNamespace, this.endpoints,
408+
httpMethod);
376409
}
377410

378411
@Override
@@ -388,7 +421,7 @@ protected ServerWebExchangeMatcher createDelegate(PathMappedEndpoints endpoints)
388421
.map(this::getEndpointId)
389422
.flatMap((endpointId) -> streamAdditionalPaths(endpoints, endpointId))
390423
.collect(Collectors.toCollection(LinkedHashSet::new));
391-
List<ServerWebExchangeMatcher> delegateMatchers = getDelegateMatchers(paths);
424+
List<ServerWebExchangeMatcher> delegateMatchers = getDelegateMatchers(paths, this.httpMethod);
392425
return (!CollectionUtils.isEmpty(delegateMatchers)) ? new OrServerWebExchangeMatcher(delegateMatchers)
393426
: EMPTY_MATCHER;
394427
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.security.servlet;
18+
19+
import java.util.function.Function;
20+
21+
import org.springframework.http.HttpMethod;
22+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
23+
import org.springframework.security.web.util.matcher.RequestMatcher;
24+
25+
/**
26+
* {@link RequestMatcherProvider} that provides an {@link AntPathRequestMatcher}.
27+
*
28+
* @author Madhura Bhave
29+
* @author Chris Bono
30+
*/
31+
class AntPathRequestMatcherProvider implements RequestMatcherProvider {
32+
33+
private final Function<String, String> pathFactory;
34+
35+
AntPathRequestMatcherProvider(Function<String, String> pathFactory) {
36+
this.pathFactory = pathFactory;
37+
}
38+
39+
@Override
40+
public RequestMatcher getRequestMatcher(String pattern, HttpMethod httpMethod) {
41+
String path = this.pathFactory.apply(pattern);
42+
return new AntPathRequestMatcher(path, (httpMethod != null) ? httpMethod.name() : null);
43+
}
44+
45+
}

0 commit comments

Comments
 (0)