Skip to content

Commit 996ee24

Browse files
onobcphilwebb
authored andcommitted
Add ability to match Endpoint requests by HTTP method
Update both servlet and reactive `EndpointRequest` classes with support for matching endpoint requests by HTTP method. See gh-29596
1 parent 2b5d8a4 commit 996ee24

File tree

8 files changed

+397
-33
lines changed

8 files changed

+397
-33
lines changed

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

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
@@ -181,11 +183,17 @@ private ServerWebExchangeMatcher createDelegate(Supplier<C> context) {
181183
protected abstract ServerWebExchangeMatcher createDelegate(C context);
182184

183185
protected final List<ServerWebExchangeMatcher> getDelegateMatchers(Set<String> paths) {
184-
return paths.stream().map(this::getDelegateMatcher).collect(Collectors.toCollection(ArrayList::new));
186+
return getDelegateMatchers(paths, null);
185187
}
186188

187-
private PathPatternParserServerWebExchangeMatcher getDelegateMatcher(String path) {
188-
return new PathPatternParserServerWebExchangeMatcher(path + "/**");
189+
protected final List<ServerWebExchangeMatcher> getDelegateMatchers(Set<String> paths, HttpMethod httpMethod) {
190+
return paths.stream()
191+
.map((path) -> getDelegateMatcher(path, httpMethod))
192+
.collect(Collectors.toCollection(ArrayList::new));
193+
}
194+
195+
private PathPatternParserServerWebExchangeMatcher getDelegateMatcher(String path, HttpMethod httpMethod) {
196+
return new PathPatternParserServerWebExchangeMatcher(path + "/**", httpMethod);
189197
}
190198

191199
@Override
@@ -258,39 +266,53 @@ public static final class EndpointServerWebExchangeMatcher extends AbstractWebEx
258266

259267
private final boolean includeLinks;
260268

269+
private final HttpMethod httpMethod;
270+
261271
private EndpointServerWebExchangeMatcher(boolean includeLinks) {
262-
this(Collections.emptyList(), Collections.emptyList(), includeLinks);
272+
this(Collections.emptyList(), Collections.emptyList(), includeLinks, null);
263273
}
264274

265275
private EndpointServerWebExchangeMatcher(Class<?>[] endpoints, boolean includeLinks) {
266-
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks);
276+
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks, null);
267277
}
268278

269279
private EndpointServerWebExchangeMatcher(String[] endpoints, boolean includeLinks) {
270-
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks);
280+
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks, null);
271281
}
272282

273-
private EndpointServerWebExchangeMatcher(List<Object> includes, List<Object> excludes, boolean includeLinks) {
283+
private EndpointServerWebExchangeMatcher(List<Object> includes, List<Object> excludes, boolean includeLinks,
284+
HttpMethod httpMethod) {
274285
super(PathMappedEndpoints.class);
275286
this.includes = includes;
276287
this.excludes = excludes;
277288
this.includeLinks = includeLinks;
289+
this.httpMethod = httpMethod;
278290
}
279291

280292
public EndpointServerWebExchangeMatcher excluding(Class<?>... endpoints) {
281293
List<Object> excludes = new ArrayList<>(this.excludes);
282294
excludes.addAll(Arrays.asList((Object[]) endpoints));
283-
return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks);
295+
return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks, null);
284296
}
285297

286298
public EndpointServerWebExchangeMatcher excluding(String... endpoints) {
287299
List<Object> excludes = new ArrayList<>(this.excludes);
288300
excludes.addAll(Arrays.asList((Object[]) endpoints));
289-
return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks);
301+
return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks, null);
290302
}
291303

292304
public EndpointServerWebExchangeMatcher excludingLinks() {
293-
return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, false);
305+
return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, false, null);
306+
}
307+
308+
/**
309+
* Restricts the matcher to only consider requests with a particular http method.
310+
* @param httpMethod the http method to include
311+
* @return a copy of the matcher further restricted to only match requests with
312+
* the specified http method
313+
*/
314+
public EndpointServerWebExchangeMatcher withHttpMethod(HttpMethod httpMethod) {
315+
return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, false, httpMethod);
294316
}
295317

296318
@Override
@@ -301,7 +323,7 @@ protected ServerWebExchangeMatcher createDelegate(PathMappedEndpoints endpoints)
301323
}
302324
streamPaths(this.includes, endpoints).forEach(paths::add);
303325
streamPaths(this.excludes, endpoints).forEach(paths::remove);
304-
List<ServerWebExchangeMatcher> delegateMatchers = getDelegateMatchers(paths);
326+
List<ServerWebExchangeMatcher> delegateMatchers = getDelegateMatchers(paths, this.httpMethod);
305327
if (this.includeLinks && StringUtils.hasText(endpoints.getBasePath())) {
306328
delegateMatchers.add(new LinksServerWebExchangeMatcher());
307329
}

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

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.context.ApplicationContext;
4343
import org.springframework.core.annotation.MergedAnnotation;
4444
import org.springframework.core.annotation.MergedAnnotations;
45+
import org.springframework.http.HttpMethod;
4546
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
4647
import org.springframework.security.web.util.matcher.OrRequestMatcher;
4748
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -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 {
@@ -212,16 +214,21 @@ protected abstract RequestMatcher createDelegate(WebApplicationContext context,
212214

213215
protected final List<RequestMatcher> getDelegateMatchers(RequestMatcherFactory requestMatcherFactory,
214216
RequestMatcherProvider matcherProvider, Set<String> paths) {
217+
return getDelegateMatchers(requestMatcherFactory, matcherProvider, paths, null);
218+
}
219+
220+
protected final List<RequestMatcher> getDelegateMatchers(RequestMatcherFactory requestMatcherFactory,
221+
RequestMatcherProvider matcherProvider, Set<String> paths, HttpMethod httpMethod) {
215222
return paths.stream()
216-
.map((path) -> requestMatcherFactory.antPath(matcherProvider, path, "/**"))
223+
.map((path) -> requestMatcherFactory.antPath(matcherProvider, httpMethod, path, "/**"))
217224
.collect(Collectors.toCollection(ArrayList::new));
218225
}
219226

220227
protected List<RequestMatcher> getLinksMatchers(RequestMatcherFactory requestMatcherFactory,
221228
RequestMatcherProvider matcherProvider, String basePath) {
222229
List<RequestMatcher> linksMatchers = new ArrayList<>();
223-
linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, basePath));
224-
linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, basePath, "/"));
230+
linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, null, basePath));
231+
linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, null, basePath, "/"));
225232
return linksMatchers;
226233
}
227234

@@ -230,7 +237,7 @@ protected RequestMatcherProvider getRequestMatcherProvider(WebApplicationContext
230237
return context.getBean(RequestMatcherProvider.class);
231238
}
232239
catch (NoSuchBeanDefinitionException ex) {
233-
return AntPathRequestMatcher::new;
240+
return (pattern, method) -> new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
234241
}
235242
}
236243

@@ -273,38 +280,52 @@ public static final class EndpointRequestMatcher extends AbstractRequestMatcher
273280

274281
private final boolean includeLinks;
275282

283+
private final HttpMethod httpMethod;
284+
276285
private EndpointRequestMatcher(boolean includeLinks) {
277-
this(Collections.emptyList(), Collections.emptyList(), includeLinks);
286+
this(Collections.emptyList(), Collections.emptyList(), includeLinks, null);
278287
}
279288

280289
private EndpointRequestMatcher(Class<?>[] endpoints, boolean includeLinks) {
281-
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks);
290+
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks, null);
282291
}
283292

284293
private EndpointRequestMatcher(String[] endpoints, boolean includeLinks) {
285-
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks);
294+
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks, null);
286295
}
287296

288-
private EndpointRequestMatcher(List<Object> includes, List<Object> excludes, boolean includeLinks) {
297+
private EndpointRequestMatcher(List<Object> includes, List<Object> excludes, boolean includeLinks,
298+
HttpMethod httpMethod) {
289299
this.includes = includes;
290300
this.excludes = excludes;
291301
this.includeLinks = includeLinks;
302+
this.httpMethod = httpMethod;
292303
}
293304

294305
public EndpointRequestMatcher excluding(Class<?>... endpoints) {
295306
List<Object> excludes = new ArrayList<>(this.excludes);
296307
excludes.addAll(Arrays.asList((Object[]) endpoints));
297-
return new EndpointRequestMatcher(this.includes, excludes, this.includeLinks);
308+
return new EndpointRequestMatcher(this.includes, excludes, this.includeLinks, null);
298309
}
299310

300311
public EndpointRequestMatcher excluding(String... endpoints) {
301312
List<Object> excludes = new ArrayList<>(this.excludes);
302313
excludes.addAll(Arrays.asList((Object[]) endpoints));
303-
return new EndpointRequestMatcher(this.includes, excludes, this.includeLinks);
314+
return new EndpointRequestMatcher(this.includes, excludes, this.includeLinks, null);
304315
}
305316

306317
public EndpointRequestMatcher excludingLinks() {
307-
return new EndpointRequestMatcher(this.includes, this.excludes, false);
318+
return new EndpointRequestMatcher(this.includes, this.excludes, false, null);
319+
}
320+
321+
/**
322+
* Restricts the matcher to only consider requests with a particular http method.
323+
* @param httpMethod the http method to include
324+
* @return a copy of the matcher further restricted to only match requests with
325+
* the specified http method
326+
*/
327+
public EndpointRequestMatcher withHttpMethod(HttpMethod httpMethod) {
328+
return new EndpointRequestMatcher(this.includes, this.excludes, false, httpMethod);
308329
}
309330

310331
@Override
@@ -318,7 +339,8 @@ protected RequestMatcher createDelegate(WebApplicationContext context,
318339
}
319340
streamPaths(this.includes, endpoints).forEach(paths::add);
320341
streamPaths(this.excludes, endpoints).forEach(paths::remove);
321-
List<RequestMatcher> delegateMatchers = getDelegateMatchers(requestMatcherFactory, matcherProvider, paths);
342+
List<RequestMatcher> delegateMatchers = getDelegateMatchers(requestMatcherFactory, matcherProvider, paths,
343+
this.httpMethod);
322344
String basePath = endpoints.getBasePath();
323345
if (this.includeLinks && StringUtils.hasText(basePath)) {
324346
delegateMatchers.addAll(getLinksMatchers(requestMatcherFactory, matcherProvider, basePath));
@@ -426,12 +448,12 @@ public String toString() {
426448
*/
427449
private static final class RequestMatcherFactory {
428450

429-
RequestMatcher antPath(RequestMatcherProvider matcherProvider, String... parts) {
451+
RequestMatcher antPath(RequestMatcherProvider matcherProvider, HttpMethod httpMethod, String... parts) {
430452
StringBuilder pattern = new StringBuilder();
431453
for (String part : parts) {
432454
pattern.append(part);
433455
}
434-
return matcherProvider.getRequestMatcher(pattern.toString());
456+
return matcherProvider.getRequestMatcher(pattern.toString(), httpMethod);
435457
}
436458

437459
}

0 commit comments

Comments
 (0)