Merge branch '2.0.x'
This commit is contained in:
commit
d724f154f4
|
@ -33,6 +33,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
|
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
|
||||||
|
import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider;
|
||||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
|
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
|
||||||
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
|
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
|
@ -158,13 +159,25 @@ public final class EndpointRequest {
|
||||||
RequestMatcherFactory requestMatcherFactory);
|
RequestMatcherFactory requestMatcherFactory);
|
||||||
|
|
||||||
protected List<RequestMatcher> getLinksMatchers(
|
protected List<RequestMatcher> getLinksMatchers(
|
||||||
RequestMatcherFactory requestMatcherFactory, String basePath) {
|
RequestMatcherFactory requestMatcherFactory,
|
||||||
|
RequestMatcherProvider matcherProvider, String basePath) {
|
||||||
List<RequestMatcher> linksMatchers = new ArrayList<>();
|
List<RequestMatcher> linksMatchers = new ArrayList<>();
|
||||||
linksMatchers.add(requestMatcherFactory.antPath(basePath));
|
linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, basePath));
|
||||||
linksMatchers.add(requestMatcherFactory.antPath(basePath, "/"));
|
linksMatchers
|
||||||
|
.add(requestMatcherFactory.antPath(matcherProvider, basePath, "/"));
|
||||||
return linksMatchers;
|
return linksMatchers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected RequestMatcherProvider getRequestMatcherProvider(
|
||||||
|
WebApplicationContext context) {
|
||||||
|
try {
|
||||||
|
return context.getBean(RequestMatcherProvider.class);
|
||||||
|
}
|
||||||
|
catch (NoSuchBeanDefinitionException ex) {
|
||||||
|
return AntPathRequestMatcher::new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -220,6 +233,7 @@ public final class EndpointRequest {
|
||||||
RequestMatcherFactory requestMatcherFactory) {
|
RequestMatcherFactory requestMatcherFactory) {
|
||||||
PathMappedEndpoints pathMappedEndpoints = context
|
PathMappedEndpoints pathMappedEndpoints = context
|
||||||
.getBean(PathMappedEndpoints.class);
|
.getBean(PathMappedEndpoints.class);
|
||||||
|
RequestMatcherProvider matcherProvider = getRequestMatcherProvider(context);
|
||||||
Set<String> paths = new LinkedHashSet<>();
|
Set<String> paths = new LinkedHashSet<>();
|
||||||
if (this.includes.isEmpty()) {
|
if (this.includes.isEmpty()) {
|
||||||
paths.addAll(pathMappedEndpoints.getAllPaths());
|
paths.addAll(pathMappedEndpoints.getAllPaths());
|
||||||
|
@ -227,11 +241,11 @@ public final class EndpointRequest {
|
||||||
streamPaths(this.includes, pathMappedEndpoints).forEach(paths::add);
|
streamPaths(this.includes, pathMappedEndpoints).forEach(paths::add);
|
||||||
streamPaths(this.excludes, pathMappedEndpoints).forEach(paths::remove);
|
streamPaths(this.excludes, pathMappedEndpoints).forEach(paths::remove);
|
||||||
List<RequestMatcher> delegateMatchers = getDelegateMatchers(
|
List<RequestMatcher> delegateMatchers = getDelegateMatchers(
|
||||||
requestMatcherFactory, paths);
|
requestMatcherFactory, matcherProvider, paths);
|
||||||
String basePath = pathMappedEndpoints.getBasePath();
|
String basePath = pathMappedEndpoints.getBasePath();
|
||||||
if (this.includeLinks && StringUtils.hasText(basePath)) {
|
if (this.includeLinks && StringUtils.hasText(basePath)) {
|
||||||
delegateMatchers
|
delegateMatchers.addAll(getLinksMatchers(requestMatcherFactory,
|
||||||
.addAll(getLinksMatchers(requestMatcherFactory, basePath));
|
matcherProvider, basePath));
|
||||||
}
|
}
|
||||||
return new OrRequestMatcher(delegateMatchers);
|
return new OrRequestMatcher(delegateMatchers);
|
||||||
}
|
}
|
||||||
|
@ -261,9 +275,10 @@ public final class EndpointRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<RequestMatcher> getDelegateMatchers(
|
private List<RequestMatcher> getDelegateMatchers(
|
||||||
RequestMatcherFactory requestMatcherFactory, Set<String> paths) {
|
RequestMatcherFactory requestMatcherFactory,
|
||||||
return paths.stream()
|
RequestMatcherProvider matcherProvider, Set<String> paths) {
|
||||||
.map((path) -> requestMatcherFactory.antPath(path, "/**"))
|
return paths.stream().map(
|
||||||
|
(path) -> requestMatcherFactory.antPath(matcherProvider, path, "/**"))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,8 +296,8 @@ public final class EndpointRequest {
|
||||||
.getBean(WebEndpointProperties.class);
|
.getBean(WebEndpointProperties.class);
|
||||||
String basePath = properties.getBasePath();
|
String basePath = properties.getBasePath();
|
||||||
if (StringUtils.hasText(basePath)) {
|
if (StringUtils.hasText(basePath)) {
|
||||||
return new OrRequestMatcher(
|
return new OrRequestMatcher(getLinksMatchers(requestMatcherFactory,
|
||||||
getLinksMatchers(requestMatcherFactory, basePath));
|
getRequestMatcherProvider(context), basePath));
|
||||||
}
|
}
|
||||||
return EMPTY_MATCHER;
|
return EMPTY_MATCHER;
|
||||||
}
|
}
|
||||||
|
@ -300,12 +315,13 @@ public final class EndpointRequest {
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RequestMatcher antPath(String... parts) {
|
public RequestMatcher antPath(RequestMatcherProvider matcherProvider,
|
||||||
|
String... parts) {
|
||||||
String pattern = this.prefix;
|
String pattern = this.prefix;
|
||||||
for (String part : parts) {
|
for (String part : parts) {
|
||||||
pattern += part;
|
pattern += part;
|
||||||
}
|
}
|
||||||
return new AntPathRequestMatcher(pattern);
|
return matcherProvider.getRequestMatcher(pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
|
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
|
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
|
||||||
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint;
|
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint;
|
||||||
|
import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider;
|
||||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
|
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockServletContext;
|
import org.springframework.mock.web.MockServletContext;
|
||||||
|
@ -214,6 +215,26 @@ public class EndpointRequestTests {
|
||||||
assertMatcher.matches("/bar");
|
assertMatcher.matches("/bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void endpointRequestMatcherShouldUseCustomRequestMatcherProvider() {
|
||||||
|
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
|
||||||
|
RequestMatcher mockRequestMatcher = (request) -> false;
|
||||||
|
RequestMatcherAssert assertMatcher = assertMatcher(matcher,
|
||||||
|
mockPathMappedEndpoints(""), "", (pattern) -> mockRequestMatcher);
|
||||||
|
assertMatcher.doesNotMatch("/foo");
|
||||||
|
assertMatcher.doesNotMatch("/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void linksRequestMatcherShouldUseCustomRequestMatcherProvider() {
|
||||||
|
RequestMatcher matcher = EndpointRequest.toLinks();
|
||||||
|
RequestMatcher mockRequestMatcher = (request) -> false;
|
||||||
|
RequestMatcherAssert assertMatcher = assertMatcher(matcher,
|
||||||
|
mockPathMappedEndpoints("/actuator"), "",
|
||||||
|
(pattern) -> mockRequestMatcher);
|
||||||
|
assertMatcher.doesNotMatch("/actuator");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noEndpointPathsBeansShouldNeverMatch() {
|
public void noEndpointPathsBeansShouldNeverMatch() {
|
||||||
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
|
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
|
||||||
|
@ -231,7 +252,8 @@ public class EndpointRequestTests {
|
||||||
|
|
||||||
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String basePath,
|
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String basePath,
|
||||||
String servletPath) {
|
String servletPath) {
|
||||||
return assertMatcher(matcher, mockPathMappedEndpoints(basePath), servletPath);
|
return assertMatcher(matcher, mockPathMappedEndpoints(basePath), servletPath,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PathMappedEndpoints mockPathMappedEndpoints(String basePath) {
|
private PathMappedEndpoints mockPathMappedEndpoints(String basePath) {
|
||||||
|
@ -250,11 +272,12 @@ public class EndpointRequestTests {
|
||||||
|
|
||||||
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
|
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
|
||||||
PathMappedEndpoints pathMappedEndpoints) {
|
PathMappedEndpoints pathMappedEndpoints) {
|
||||||
return assertMatcher(matcher, pathMappedEndpoints, "");
|
return assertMatcher(matcher, pathMappedEndpoints, "", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
|
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
|
||||||
PathMappedEndpoints pathMappedEndpoints, String dispatcherServletPath) {
|
PathMappedEndpoints pathMappedEndpoints, String dispatcherServletPath,
|
||||||
|
RequestMatcherProvider matcherProvider) {
|
||||||
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
||||||
context.registerBean(WebEndpointProperties.class);
|
context.registerBean(WebEndpointProperties.class);
|
||||||
if (pathMappedEndpoints != null) {
|
if (pathMappedEndpoints != null) {
|
||||||
|
@ -269,6 +292,9 @@ public class EndpointRequestTests {
|
||||||
DispatcherServletPath path = () -> dispatcherServletPath;
|
DispatcherServletPath path = () -> dispatcherServletPath;
|
||||||
context.registerBean(DispatcherServletPath.class, () -> path);
|
context.registerBean(DispatcherServletPath.class, () -> path);
|
||||||
}
|
}
|
||||||
|
if (matcherProvider != null) {
|
||||||
|
context.registerBean(RequestMatcherProvider.class, () -> matcherProvider);
|
||||||
|
}
|
||||||
return assertThat(new RequestMatcherAssert(context, matcher));
|
return assertThat(new RequestMatcherAssert(context, matcher));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
@ -49,6 +50,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.servlet.HandlerMapping;
|
import org.springframework.web.servlet.HandlerMapping;
|
||||||
|
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
|
||||||
|
import org.springframework.web.servlet.handler.RequestMatchResult;
|
||||||
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
|
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
|
||||||
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
|
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
|
||||||
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
|
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
|
||||||
|
@ -66,7 +69,8 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractWebMvcEndpointHandlerMapping
|
public abstract class AbstractWebMvcEndpointHandlerMapping
|
||||||
extends RequestMappingInfoHandlerMapping implements InitializingBean {
|
extends RequestMappingInfoHandlerMapping
|
||||||
|
implements InitializingBean, MatchableHandlerMapping {
|
||||||
|
|
||||||
private final EndpointMapping endpointMapping;
|
private final EndpointMapping endpointMapping;
|
||||||
|
|
||||||
|
@ -82,6 +86,8 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
|
||||||
private final Method handleMethod = ReflectionUtils.findMethod(OperationHandler.class,
|
private final Method handleMethod = ReflectionUtils.findMethod(OperationHandler.class,
|
||||||
"handle", HttpServletRequest.class, Map.class);
|
"handle", HttpServletRequest.class, Map.class);
|
||||||
|
|
||||||
|
private static final RequestMappingInfo.BuilderConfiguration builderConfig = getBuilderConfig();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||||
* operations of the given {@code webEndpoints}.
|
* operations of the given {@code webEndpoints}.
|
||||||
|
@ -125,6 +131,29 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestMatchResult match(HttpServletRequest request, String pattern) {
|
||||||
|
RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(builderConfig)
|
||||||
|
.build();
|
||||||
|
RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
|
||||||
|
if (matchingInfo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Set<String> patterns = matchingInfo.getPatternsCondition().getPatterns();
|
||||||
|
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
|
||||||
|
return new RequestMatchResult(patterns.iterator().next(), lookupPath,
|
||||||
|
getPathMatcher());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RequestMappingInfo.BuilderConfiguration getBuilderConfig() {
|
||||||
|
RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
|
||||||
|
config.setUrlPathHelper(null);
|
||||||
|
config.setPathMatcher(null);
|
||||||
|
config.setSuffixPatternMatch(false);
|
||||||
|
config.setTrailingSlashMatch(true);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
private void registerMappingForOperation(ExposableWebEndpoint endpoint,
|
private void registerMappingForOperation(ExposableWebEndpoint endpoint,
|
||||||
WebOperation operation) {
|
WebOperation operation) {
|
||||||
OperationInvoker invoker = operation::invoke;
|
OperationInvoker invoker = operation::invoke;
|
||||||
|
@ -176,7 +205,9 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
|
||||||
|
|
||||||
private PatternsRequestCondition patternsRequestConditionForPattern(String path) {
|
private PatternsRequestCondition patternsRequestConditionForPattern(String path) {
|
||||||
String[] patterns = new String[] { this.endpointMapping.createSubPath(path) };
|
String[] patterns = new String[] { this.endpointMapping.createSubPath(path) };
|
||||||
return new PatternsRequestCondition(patterns, null, null, false, true);
|
return new PatternsRequestCondition(patterns, builderConfig.getUrlPathHelper(),
|
||||||
|
builderConfig.getPathMatcher(), builderConfig.useSuffixPatternMatch(),
|
||||||
|
builderConfig.useTrailingSlashMatch());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -46,6 +46,7 @@ import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
@ -53,6 +54,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
|
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
import org.springframework.web.servlet.handler.RequestMatchResult;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@ -104,6 +106,27 @@ public class MvcWebEndpointIntegrationTests extends
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchWhenRequestHasTrailingSlashShouldNotBeNull() {
|
||||||
|
assertThat(getMatchResult("/spring/")).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchWhenRequestHasSuffixShouldBeNull() {
|
||||||
|
assertThat(getMatchResult("/spring.do")).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestMatchResult getMatchResult(String s) {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setServletPath(s);
|
||||||
|
AnnotationConfigServletWebServerApplicationContext context = createApplicationContext();
|
||||||
|
context.register(TestEndpointConfiguration.class);
|
||||||
|
context.refresh();
|
||||||
|
WebMvcEndpointHandlerMapping bean = context
|
||||||
|
.getBean(WebMvcEndpointHandlerMapping.class);
|
||||||
|
return bean.match(request, "/spring");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getPort(AnnotationConfigServletWebServerApplicationContext context) {
|
protected int getPort(AnnotationConfigServletWebServerApplicationContext context) {
|
||||||
return context.getWebServer().getPort();
|
return context.getWebServer().getPort();
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.boot.autoconfigure.security.servlet;
|
||||||
|
|
||||||
|
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link RequestMatcherProvider} that provides an {@link MvcRequestMatcher} that can be
|
||||||
|
* used for Spring MVC applications.
|
||||||
|
*
|
||||||
|
* @author Madhura Bhave
|
||||||
|
*/
|
||||||
|
public class MvcRequestMatcherProvider implements RequestMatcherProvider {
|
||||||
|
|
||||||
|
private final HandlerMappingIntrospector introspector;
|
||||||
|
|
||||||
|
public MvcRequestMatcherProvider(HandlerMappingIntrospector introspector) {
|
||||||
|
this.introspector = introspector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestMatcher getRequestMatcher(String pattern) {
|
||||||
|
return new MvcRequestMatcher(this.introspector, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.boot.autoconfigure.security.servlet;
|
||||||
|
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that can be used to provide a {@link RequestMatcher} that can be used with
|
||||||
|
* Spring Security.
|
||||||
|
*
|
||||||
|
* @author Madhura Bhave
|
||||||
|
* @since 2.0.5
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface RequestMatcherProvider {
|
||||||
|
|
||||||
|
RequestMatcher getRequestMatcher(String pattern);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.boot.autoconfigure.security.servlet;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
import org.springframework.web.servlet.DispatcherServlet;
|
||||||
|
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-configuration for {@link RequestMatcherProvider}.
|
||||||
|
*
|
||||||
|
* @author Madhura Bhave
|
||||||
|
* @since 2.0.5
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnClass({ RequestMatcher.class, DispatcherServlet.class })
|
||||||
|
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||||
|
@ConditionalOnBean(HandlerMappingIntrospector.class)
|
||||||
|
public class SecurityRequestMatcherProviderAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RequestMatcherProvider requestMatcherProvider(
|
||||||
|
HandlerMappingIntrospector introspector) {
|
||||||
|
return new MvcRequestMatcherProvider(introspector);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -98,6 +98,7 @@ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
|
||||||
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
|
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
|
||||||
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
|
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
|
||||||
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
|
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
|
||||||
|
org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration,\
|
||||||
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
|
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
|
||||||
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
|
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
|
||||||
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
|
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.boot.autoconfigure.security.servlet;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link SecurityRequestMatcherProviderAutoConfiguration}.
|
||||||
|
*
|
||||||
|
* @author Madhura Bhave
|
||||||
|
*/
|
||||||
|
public class SecurityRequestMatcherProviderAutoConfigurationTests {
|
||||||
|
|
||||||
|
private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
|
||||||
|
.withConfiguration(AutoConfigurations
|
||||||
|
.of(SecurityRequestMatcherProviderAutoConfiguration.class))
|
||||||
|
.withUserConfiguration(TestConfiguration.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void registersMvcRequestMatcherProviderForIfMvcPresent() {
|
||||||
|
this.contextRunner.run((context) -> assertThat(context)
|
||||||
|
.hasSingleBean(MvcRequestMatcherProvider.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mvcRequestMatcherProviderConditionalOnWebApplication() {
|
||||||
|
new ApplicationContextRunner()
|
||||||
|
.withConfiguration(AutoConfigurations
|
||||||
|
.of(SecurityRequestMatcherProviderAutoConfiguration.class))
|
||||||
|
.withUserConfiguration(TestConfiguration.class)
|
||||||
|
.run((context) -> assertThat(context)
|
||||||
|
.doesNotHaveBean(MvcRequestMatcherProvider.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mvcRequestMatcherProviderConditionalOnDispatcherServletClass() {
|
||||||
|
this.contextRunner
|
||||||
|
.withClassLoader(new FilteredClassLoader(
|
||||||
|
"org.springframework.web.servlet.DispatcherServlet"))
|
||||||
|
.run((context) -> assertThat(context)
|
||||||
|
.doesNotHaveBean(MvcRequestMatcherProvider.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mvcRequestMatcherProviderConditionalOnRequestMatcherClass() {
|
||||||
|
this.contextRunner
|
||||||
|
.withClassLoader(new FilteredClassLoader(
|
||||||
|
"org.springframework.security.web.util.matcher.RequestMatcher"))
|
||||||
|
.run((context) -> assertThat(context)
|
||||||
|
.doesNotHaveBean(MvcRequestMatcherProvider.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mvcRequestMatcherProviderConditionalOnHandlerMappingIntrospectorBean() {
|
||||||
|
new WebApplicationContextRunner()
|
||||||
|
.withConfiguration(AutoConfigurations
|
||||||
|
.of(SecurityRequestMatcherProviderAutoConfiguration.class))
|
||||||
|
.run((context) -> assertThat(context)
|
||||||
|
.doesNotHaveBean(MvcRequestMatcherProvider.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class TestConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public HandlerMappingIntrospector introspector() {
|
||||||
|
return new HandlerMappingIntrospector();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,6 +35,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
return new InMemoryUserDetailsManager(
|
return new InMemoryUserDetailsManager(
|
||||||
User.withDefaultPasswordEncoder().username("user").password("password")
|
User.withDefaultPasswordEncoder().username("user").password("password")
|
||||||
.authorities("ROLE_USER").build(),
|
.authorities("ROLE_USER").build(),
|
||||||
|
User.withDefaultPasswordEncoder().username("beans").password("beans")
|
||||||
|
.authorities("ROLE_BEANS").build(),
|
||||||
User.withDefaultPasswordEncoder().username("admin").password("admin")
|
User.withDefaultPasswordEncoder().username("admin").password("admin")
|
||||||
.authorities("ROLE_ACTUATOR", "ROLE_USER").build());
|
.authorities("ROLE_ACTUATOR", "ROLE_USER").build());
|
||||||
}
|
}
|
||||||
|
@ -43,6 +45,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
http.authorizeRequests()
|
http.authorizeRequests()
|
||||||
|
.mvcMatchers("/actuator/beans").hasRole("BEANS")
|
||||||
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
|
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
|
||||||
.requestMatchers(EndpointRequest.toAnyEndpoint().excluding(MappingsEndpoint.class)).hasRole("ACTUATOR")
|
.requestMatchers(EndpointRequest.toAnyEndpoint().excluding(MappingsEndpoint.class)).hasRole("ACTUATOR")
|
||||||
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
|
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
|
||||||
|
|
|
@ -141,6 +141,16 @@ public class SampleActuatorCustomSecurityApplicationTests {
|
||||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mvcMatchersCanBeUsedToSecureActuators() {
|
||||||
|
ResponseEntity<Object> entity = beansRestTemplate()
|
||||||
|
.getForEntity("/actuator/beans", Object.class);
|
||||||
|
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
entity = beansRestTemplate()
|
||||||
|
.getForEntity("/actuator/beans/", Object.class);
|
||||||
|
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
private TestRestTemplate restTemplate() {
|
private TestRestTemplate restTemplate() {
|
||||||
return configure(new TestRestTemplate());
|
return configure(new TestRestTemplate());
|
||||||
}
|
}
|
||||||
|
@ -153,6 +163,10 @@ public class SampleActuatorCustomSecurityApplicationTests {
|
||||||
return configure(new TestRestTemplate("user", "password"));
|
return configure(new TestRestTemplate("user", "password"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TestRestTemplate beansRestTemplate() {
|
||||||
|
return configure(new TestRestTemplate("beans", "beans"));
|
||||||
|
}
|
||||||
|
|
||||||
private TestRestTemplate configure(TestRestTemplate restTemplate) {
|
private TestRestTemplate configure(TestRestTemplate restTemplate) {
|
||||||
restTemplate
|
restTemplate
|
||||||
.setUriTemplateHandler(new LocalHostUriTemplateHandler(this.environment));
|
.setUriTemplateHandler(new LocalHostUriTemplateHandler(this.environment));
|
||||||
|
|
Loading…
Reference in New Issue