Skip to content

Commit 9576293

Browse files
committed
Polish 'Add ability to match Endpoint requests by HTTP method'
See gh-29596
1 parent 996ee24 commit 9576293

File tree

12 files changed

+170
-60
lines changed

12 files changed

+170
-60
lines changed

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

Lines changed: 21 additions & 10 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.
@@ -182,10 +182,6 @@ private ServerWebExchangeMatcher createDelegate(Supplier<C> context) {
182182

183183
protected abstract ServerWebExchangeMatcher createDelegate(C context);
184184

185-
protected final List<ServerWebExchangeMatcher> getDelegateMatchers(Set<String> paths) {
186-
return getDelegateMatchers(paths, null);
187-
}
188-
189185
protected final List<ServerWebExchangeMatcher> getDelegateMatchers(Set<String> paths, HttpMethod httpMethod) {
190186
return paths.stream()
191187
.map((path) -> getDelegateMatcher(path, httpMethod))
@@ -312,7 +308,7 @@ public EndpointServerWebExchangeMatcher excludingLinks() {
312308
* the specified http method
313309
*/
314310
public EndpointServerWebExchangeMatcher withHttpMethod(HttpMethod httpMethod) {
315-
return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, false, httpMethod);
311+
return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, this.includeLinks, httpMethod);
316312
}
317313

318314
@Override
@@ -379,22 +375,37 @@ public static class AdditionalPathsEndpointServerWebExchangeMatcher
379375

380376
private final List<Object> endpoints;
381377

378+
private final HttpMethod httpMethod;
379+
382380
AdditionalPathsEndpointServerWebExchangeMatcher(WebServerNamespace webServerNamespace, String... endpoints) {
383-
this(webServerNamespace, Arrays.asList((Object[]) endpoints));
381+
this(webServerNamespace, Arrays.asList((Object[]) endpoints), null);
384382
}
385383

386384
AdditionalPathsEndpointServerWebExchangeMatcher(WebServerNamespace webServerNamespace, Class<?>... endpoints) {
387-
this(webServerNamespace, Arrays.asList((Object[]) endpoints));
385+
this(webServerNamespace, Arrays.asList((Object[]) endpoints), null);
388386
}
389387

390388
private AdditionalPathsEndpointServerWebExchangeMatcher(WebServerNamespace webServerNamespace,
391-
List<Object> endpoints) {
389+
List<Object> endpoints, HttpMethod httpMethod) {
392390
super(PathMappedEndpoints.class);
393391
Assert.notNull(webServerNamespace, "'webServerNamespace' must not be null");
394392
Assert.notNull(endpoints, "'endpoints' must not be null");
395393
Assert.notEmpty(endpoints, "'endpoints' must not be empty");
396394
this.webServerNamespace = webServerNamespace;
397395
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);
398409
}
399410

400411
@Override
@@ -410,7 +421,7 @@ protected ServerWebExchangeMatcher createDelegate(PathMappedEndpoints endpoints)
410421
.map(this::getEndpointId)
411422
.flatMap((endpointId) -> streamAdditionalPaths(endpoints, endpointId))
412423
.collect(Collectors.toCollection(LinkedHashSet::new));
413-
List<ServerWebExchangeMatcher> delegateMatchers = getDelegateMatchers(paths);
424+
List<ServerWebExchangeMatcher> delegateMatchers = getDelegateMatchers(paths, this.httpMethod);
414425
return (!CollectionUtils.isEmpty(delegateMatchers)) ? new OrServerWebExchangeMatcher(delegateMatchers)
415426
: EMPTY_MATCHER;
416427
}
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+
}

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

Lines changed: 45 additions & 18 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.
@@ -36,7 +36,6 @@
3636
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
3737
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
3838
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
39-
import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider;
4039
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
4140
import org.springframework.boot.web.context.WebServerApplicationContext;
4241
import org.springframework.context.ApplicationContext;
@@ -212,11 +211,6 @@ private RequestMatcher createDelegate(WebApplicationContext context) {
212211
protected abstract RequestMatcher createDelegate(WebApplicationContext context,
213212
RequestMatcherFactory requestMatcherFactory);
214213

215-
protected final List<RequestMatcher> getDelegateMatchers(RequestMatcherFactory requestMatcherFactory,
216-
RequestMatcherProvider matcherProvider, Set<String> paths) {
217-
return getDelegateMatchers(requestMatcherFactory, matcherProvider, paths, null);
218-
}
219-
220214
protected final List<RequestMatcher> getDelegateMatchers(RequestMatcherFactory requestMatcherFactory,
221215
RequestMatcherProvider matcherProvider, Set<String> paths, HttpMethod httpMethod) {
222216
return paths.stream()
@@ -234,21 +228,37 @@ protected List<RequestMatcher> getLinksMatchers(RequestMatcherFactory requestMat
234228

235229
protected RequestMatcherProvider getRequestMatcherProvider(WebApplicationContext context) {
236230
try {
237-
return context.getBean(RequestMatcherProvider.class);
231+
return getRequestMatcherProviderBean(context);
238232
}
239233
catch (NoSuchBeanDefinitionException ex) {
240234
return (pattern, method) -> new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
241235
}
242236
}
243237

244-
protected final String toString(List<Object> endpoints, String emptyValue) {
238+
private RequestMatcherProvider getRequestMatcherProviderBean(WebApplicationContext context) {
239+
try {
240+
return context.getBean(RequestMatcherProvider.class);
241+
}
242+
catch (NoSuchBeanDefinitionException ex) {
243+
return getAndAdaptDeprecatedRequestMatcherProviderBean(context);
244+
}
245+
}
246+
247+
@SuppressWarnings("removal")
248+
private RequestMatcherProvider getAndAdaptDeprecatedRequestMatcherProviderBean(WebApplicationContext context) {
249+
org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider bean = context
250+
.getBean(org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider.class);
251+
return (pattern, method) -> bean.getRequestMatcher(pattern);
252+
}
253+
254+
protected String toString(List<Object> endpoints, String emptyValue) {
245255
return (!endpoints.isEmpty()) ? endpoints.stream()
246256
.map(this::getEndpointId)
247257
.map(Object::toString)
248258
.collect(Collectors.joining(", ", "[", "]")) : emptyValue;
249259
}
250260

251-
protected final EndpointId getEndpointId(Object source) {
261+
protected EndpointId getEndpointId(Object source) {
252262
if (source instanceof EndpointId endpointId) {
253263
return endpointId;
254264
}
@@ -319,13 +329,14 @@ public EndpointRequestMatcher excludingLinks() {
319329
}
320330

321331
/**
322-
* Restricts the matcher to only consider requests with a particular http method.
323-
* @param httpMethod the http method to include
332+
* Restricts the matcher to only consider requests with a particular HTTP method.
333+
* @param httpMethod the HTTP method to include
324334
* @return a copy of the matcher further restricted to only match requests with
325-
* the specified http method
335+
* the specified HTTP method
336+
* @since 3.5.0
326337
*/
327338
public EndpointRequestMatcher withHttpMethod(HttpMethod httpMethod) {
328-
return new EndpointRequestMatcher(this.includes, this.excludes, false, httpMethod);
339+
return new EndpointRequestMatcher(this.includes, this.excludes, this.includeLinks, httpMethod);
329340
}
330341

331342
@Override
@@ -394,20 +405,35 @@ public static class AdditionalPathsEndpointRequestMatcher extends AbstractReques
394405

395406
private final List<Object> endpoints;
396407

408+
private final HttpMethod httpMethod;
409+
397410
AdditionalPathsEndpointRequestMatcher(WebServerNamespace webServerNamespace, String... endpoints) {
398-
this(webServerNamespace, Arrays.asList((Object[]) endpoints));
411+
this(webServerNamespace, Arrays.asList((Object[]) endpoints), null);
399412
}
400413

401414
AdditionalPathsEndpointRequestMatcher(WebServerNamespace webServerNamespace, Class<?>... endpoints) {
402-
this(webServerNamespace, Arrays.asList((Object[]) endpoints));
415+
this(webServerNamespace, Arrays.asList((Object[]) endpoints), null);
403416
}
404417

405-
private AdditionalPathsEndpointRequestMatcher(WebServerNamespace webServerNamespace, List<Object> endpoints) {
418+
private AdditionalPathsEndpointRequestMatcher(WebServerNamespace webServerNamespace, List<Object> endpoints,
419+
HttpMethod httpMethod) {
406420
Assert.notNull(webServerNamespace, "'webServerNamespace' must not be null");
407421
Assert.notNull(endpoints, "'endpoints' must not be null");
408422
Assert.notEmpty(endpoints, "'endpoints' must not be empty");
409423
this.webServerNamespace = webServerNamespace;
410424
this.endpoints = endpoints;
425+
this.httpMethod = httpMethod;
426+
}
427+
428+
/**
429+
* Restricts the matcher to only consider requests with a particular HTTP method.
430+
* @param httpMethod the HTTP method to include
431+
* @return a copy of the matcher further restricted to only match requests with
432+
* the specified HTTP method
433+
* @since 3.5.0
434+
*/
435+
public AdditionalPathsEndpointRequestMatcher withHttpMethod(HttpMethod httpMethod) {
436+
return new AdditionalPathsEndpointRequestMatcher(this.webServerNamespace, this.endpoints, httpMethod);
411437
}
412438

413439
@Override
@@ -426,7 +452,8 @@ protected RequestMatcher createDelegate(WebApplicationContext context,
426452
.map(this::getEndpointId)
427453
.flatMap((endpointId) -> streamAdditionalPaths(endpoints, endpointId))
428454
.collect(Collectors.toCollection(LinkedHashSet::new));
429-
List<RequestMatcher> delegateMatchers = getDelegateMatchers(requestMatcherFactory, matcherProvider, paths);
455+
List<RequestMatcher> delegateMatchers = getDelegateMatchers(requestMatcherFactory, matcherProvider, paths,
456+
this.httpMethod);
430457
return (!CollectionUtils.isEmpty(delegateMatchers)) ? new OrRequestMatcher(delegateMatchers)
431458
: EMPTY_MATCHER;
432459
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2012-2020 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 org.springframework.http.HttpMethod;
20+
import org.springframework.security.web.util.matcher.RequestMatcher;
21+
22+
/**
23+
* Interface that can be used to provide a {@link RequestMatcher} that can be used with
24+
* Spring Security.
25+
*
26+
* @author Madhura Bhave
27+
* @author Chris Bono
28+
* @since 3.5.0
29+
*/
30+
@FunctionalInterface
31+
public interface RequestMatcherProvider {
32+
33+
/**
34+
* Return the {@link RequestMatcher} to be used for the specified pattern and http
35+
* method.
36+
* @param pattern the request pattern
37+
* @param httpMethod the http method
38+
* @return a request matcher
39+
*/
40+
RequestMatcher getRequestMatcher(String pattern, HttpMethod httpMethod);
41+
42+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/SecurityRequestMatchersManagementContextConfiguration.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2525
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
2626
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
27-
import org.springframework.boot.autoconfigure.security.servlet.AntPathRequestMatcherProvider;
28-
import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider;
2927
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
3028
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
3129
import org.springframework.context.annotation.Bean;

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 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.

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java

Lines changed: 1 addition & 1 deletion
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.

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java

Lines changed: 1 addition & 1 deletion
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.

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java

Lines changed: 1 addition & 2 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.
@@ -34,7 +34,6 @@
3434
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
3535
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
3636
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
37-
import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider;
3837
import org.springframework.boot.web.context.WebServerApplicationContext;
3938
import org.springframework.boot.web.server.WebServer;
4039
import org.springframework.http.HttpMethod;

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/SecurityRequestMatchersManagementContextConfigurationTests.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
import org.junit.jupiter.api.Test;
2020

2121
import org.springframework.boot.autoconfigure.AutoConfigurations;
22-
import org.springframework.boot.autoconfigure.security.servlet.AntPathRequestMatcherProvider;
23-
import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider;
2422
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
2523
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
2624
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -61,7 +59,7 @@ void configurationConditionalOnRequestMatcherClass() {
6159
void registersRequestMatcherProviderIfMvcPresent() {
6260
this.contextRunner.withUserConfiguration(TestMvcConfiguration.class).run((context) -> {
6361
AntPathRequestMatcherProvider matcherProvider = context.getBean(AntPathRequestMatcherProvider.class);
64-
RequestMatcher requestMatcher = matcherProvider.getRequestMatcher("/example");
62+
RequestMatcher requestMatcher = matcherProvider.getRequestMatcher("/example", null);
6563
assertThat(requestMatcher).extracting("pattern").isEqualTo("/custom/example");
6664
});
6765
}
@@ -72,7 +70,7 @@ void registersRequestMatcherForJerseyProviderIfJerseyPresentAndMvcAbsent() {
7270
.withUserConfiguration(TestJerseyConfiguration.class)
7371
.run((context) -> {
7472
AntPathRequestMatcherProvider matcherProvider = context.getBean(AntPathRequestMatcherProvider.class);
75-
RequestMatcher requestMatcher = matcherProvider.getRequestMatcher("/example");
73+
RequestMatcher requestMatcher = matcherProvider.getRequestMatcher("/example", null);
7674
assertThat(requestMatcher).extracting("pattern").isEqualTo("/admin/example");
7775
});
7876
}

0 commit comments

Comments
 (0)