Rework security request matchers
Update the security request matchers so that a bean is no longer needed when the matcher is used. Matchers can now be build by starting from the `EndpointRequest` or `StaticResourceRequest` classes. For example: http.authorizeRequests() .requestMatchers(EndpointRequest.to("status", "info")).permitAll() .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR") .requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll() Closes gh-7958pull/9995/merge
parent
2e51b48cd9
commit
46dfe38b60
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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.actuate.autoconfigure.endpoint.web;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
|
||||||
|
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||||
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default {@link EndpointPathProvider} implementation.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class DefaultEndpointPathProvider implements EndpointPathProvider {
|
||||||
|
|
||||||
|
private final Collection<EndpointInfo<WebEndpointOperation>> endpoints;
|
||||||
|
|
||||||
|
private final String contextPath;
|
||||||
|
|
||||||
|
public DefaultEndpointPathProvider(EndpointProvider<WebEndpointOperation> provider,
|
||||||
|
ManagementServerProperties managementServerProperties) {
|
||||||
|
this.endpoints = provider.getEndpoints();
|
||||||
|
this.contextPath = managementServerProperties.getContextPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getPaths() {
|
||||||
|
return this.endpoints.stream().map(this::getPath).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath(String id) {
|
||||||
|
Assert.notNull(id, "ID must not be null");
|
||||||
|
return this.endpoints.stream().filter((info) -> id.equals(info.getId()))
|
||||||
|
.findFirst().map(this::getPath).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPath(EndpointInfo<WebEndpointOperation> endpointInfo) {
|
||||||
|
return this.contextPath + "/" + endpointInfo.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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.actuate.autoconfigure.endpoint.web;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that provides path information for web mapped endpints.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public interface EndpointPathProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all mapped endpoint paths.
|
||||||
|
* @return all paths
|
||||||
|
*/
|
||||||
|
List<String> getPaths();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the path for the endpoint with the specified ID.
|
||||||
|
* @param id the endpoint ID
|
||||||
|
* @return the path of the endpoint or {@code null}
|
||||||
|
*/
|
||||||
|
String getPath(String id);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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.actuate.autoconfigure.security;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
|
||||||
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
|
import org.springframework.boot.security.ApplicationContextRequestMatcher;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory that can be used to create a {@link RequestMatcher} for actuator endpoint
|
||||||
|
* locations.
|
||||||
|
*
|
||||||
|
* @author Madhura Bhave
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public final class EndpointRequest {
|
||||||
|
|
||||||
|
private EndpointRequest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a matcher that includes all {@link Endpoint actuator endpoints}. The
|
||||||
|
* {@link EndpointRequestMatcher#excluding(Class...) excluding} method can be used to
|
||||||
|
* further remove specific endpoints if required. For example: <pre class="code">
|
||||||
|
* EndpointRequestMatcher.toAnyEndpoint().excluding(ShutdownEndpoint.class)
|
||||||
|
* </pre>
|
||||||
|
* @return the configured {@link RequestMatcher}
|
||||||
|
*/
|
||||||
|
public static EndpointRequestMatcher toAnyEndpoint() {
|
||||||
|
return new EndpointRequestMatcher();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a matcher that includes the specified {@link Endpoint actuator endpoints}.
|
||||||
|
* For example: <pre class="code">
|
||||||
|
* EndpointRequestMatcher.to(ShutdownEndpoint.class, HealthEndpoint.class)
|
||||||
|
* </pre>
|
||||||
|
* @param endpoints the endpoints to include
|
||||||
|
* @return the configured {@link RequestMatcher}
|
||||||
|
*/
|
||||||
|
public static EndpointRequestMatcher to(Class<?>... endpoints) {
|
||||||
|
return new EndpointRequestMatcher(endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a matcher that includes the specified {@link Endpoint actuator endpoints}.
|
||||||
|
* For example: <pre class="code">
|
||||||
|
* EndpointRequestMatcher.to("shutdown", "health")
|
||||||
|
* </pre>
|
||||||
|
* @param endpoints the endpoints to include
|
||||||
|
* @return the configured {@link RequestMatcher}
|
||||||
|
*/
|
||||||
|
public static EndpointRequestMatcher to(String... endpoints) {
|
||||||
|
return new EndpointRequestMatcher(endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request matcher used to match against {@link Endpoint actuator endpoints}.
|
||||||
|
*/
|
||||||
|
public final static class EndpointRequestMatcher
|
||||||
|
extends ApplicationContextRequestMatcher<EndpointPathProvider> {
|
||||||
|
|
||||||
|
private final List<Object> includes;
|
||||||
|
|
||||||
|
private final List<Object> excludes;
|
||||||
|
|
||||||
|
private RequestMatcher delegate;
|
||||||
|
|
||||||
|
private EndpointRequestMatcher() {
|
||||||
|
super(EndpointPathProvider.class);
|
||||||
|
this.includes = Collections.emptyList();
|
||||||
|
this.excludes = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private EndpointRequestMatcher(Class<?>[] endpoints) {
|
||||||
|
super(EndpointPathProvider.class);
|
||||||
|
this.includes = Arrays.asList((Object[]) endpoints);
|
||||||
|
this.excludes = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private EndpointRequestMatcher(String[] endpoints) {
|
||||||
|
super(EndpointPathProvider.class);
|
||||||
|
this.includes = Arrays.asList((Object[]) endpoints);
|
||||||
|
this.excludes = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private EndpointRequestMatcher(List<Object> includes, List<Object> excludes) {
|
||||||
|
super(EndpointPathProvider.class);
|
||||||
|
this.includes = includes;
|
||||||
|
this.excludes = excludes;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndpointRequestMatcher excluding(Class<?>... endpoints) {
|
||||||
|
List<Object> excludes = new ArrayList<>(this.excludes);
|
||||||
|
excludes.addAll(Arrays.asList((Object[]) endpoints));
|
||||||
|
return new EndpointRequestMatcher(this.includes, excludes);
|
||||||
|
}
|
||||||
|
|
||||||
|
EndpointRequestMatcher excluding(String... endpoints) {
|
||||||
|
List<Object> excludes = new ArrayList<>(this.excludes);
|
||||||
|
excludes.addAll(Arrays.asList((Object[]) endpoints));
|
||||||
|
return new EndpointRequestMatcher(this.includes, excludes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initialized(EndpointPathProvider endpointPathProvider) {
|
||||||
|
Set<String> paths = new LinkedHashSet<>(this.includes.isEmpty()
|
||||||
|
? endpointPathProvider.getPaths() : Collections.emptyList());
|
||||||
|
streamPaths(this.includes, endpointPathProvider).forEach(paths::add);
|
||||||
|
streamPaths(this.excludes, endpointPathProvider).forEach(paths::remove);
|
||||||
|
this.delegate = new OrRequestMatcher(getDelegateMatchers(paths));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<String> streamPaths(List<Object> source,
|
||||||
|
EndpointPathProvider endpointPathProvider) {
|
||||||
|
return source.stream().filter(Objects::nonNull).map(this::getPathId)
|
||||||
|
.map(endpointPathProvider::getPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPathId(Object source) {
|
||||||
|
if (source instanceof String) {
|
||||||
|
return (String) source;
|
||||||
|
}
|
||||||
|
if (source instanceof Class) {
|
||||||
|
return getPathId((Class<?>) source);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Unsupported source " + source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPathId(Class<?> source) {
|
||||||
|
Endpoint annotation = AnnotationUtils.findAnnotation(source, Endpoint.class);
|
||||||
|
Assert.state(annotation != null,
|
||||||
|
"Class " + source + " is not annotated with @Endpoint");
|
||||||
|
return annotation.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RequestMatcher> getDelegateMatchers(Set<String> paths) {
|
||||||
|
return paths.stream().map((path) -> new AntPathRequestMatcher(path + "/**"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(HttpServletRequest request) {
|
||||||
|
try {
|
||||||
|
return super.matches(request);
|
||||||
|
}
|
||||||
|
catch (BeanCreationException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean matches(HttpServletRequest request,
|
||||||
|
EndpointPathProvider context) {
|
||||||
|
return this.delegate.matches(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-configuration for security.
|
||||||
|
*/
|
||||||
|
package org.springframework.boot.actuate.autoconfigure.security;
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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.actuate.autoconfigure.endpoint.web;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
|
||||||
|
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||||
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link DefaultEndpointPathProvider}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class DefaultEndpointPathProviderTests {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private EndpointProvider<WebEndpointOperation> endpointProvider;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getPathsShouldReturnAllPaths() throws Exception {
|
||||||
|
DefaultEndpointPathProvider provider = createProvider("");
|
||||||
|
assertThat(provider.getPaths()).containsOnly("/foo", "/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getPathsWhenHasContextPathShouldReturnAllPathsWithContext()
|
||||||
|
throws Exception {
|
||||||
|
DefaultEndpointPathProvider provider = createProvider("/application");
|
||||||
|
assertThat(provider.getPaths()).containsOnly("/application/foo",
|
||||||
|
"/application/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getPathWhenEndpointIdIsKnownShouldReturnPath() throws Exception {
|
||||||
|
DefaultEndpointPathProvider provider = createProvider("");
|
||||||
|
assertThat(provider.getPath("foo")).isEqualTo("/foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getPathWhenEndpointIdIsUnknownShouldReturnNull() throws Exception {
|
||||||
|
DefaultEndpointPathProvider provider = createProvider("");
|
||||||
|
assertThat(provider.getPath("baz")).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getPathWhenHasContextPathReturnPath() throws Exception {
|
||||||
|
DefaultEndpointPathProvider provider = createProvider("/application");
|
||||||
|
assertThat(provider.getPath("foo")).isEqualTo("/application/foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
private DefaultEndpointPathProvider createProvider(String contextPath) {
|
||||||
|
Collection<EndpointInfo<WebEndpointOperation>> endpoints = new ArrayList<>();
|
||||||
|
endpoints.add(new EndpointInfo<>("foo", true, Collections.emptyList()));
|
||||||
|
endpoints.add(new EndpointInfo<>("bar", true, Collections.emptyList()));
|
||||||
|
given(this.endpointProvider.getEndpoints()).willReturn(endpoints);
|
||||||
|
ManagementServerProperties managementServerProperties = new ManagementServerProperties();
|
||||||
|
managementServerProperties.setContextPath(contextPath);
|
||||||
|
return new DefaultEndpointPathProvider(this.endpointProvider,
|
||||||
|
managementServerProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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.actuate.autoconfigure.security;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.assertj.core.api.AssertDelegateTarget;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
|
||||||
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockServletContext;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link EndpointRequest}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class EndpointRequestTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toAnyEndpointShouldMatchEndpointPath() throws Exception {
|
||||||
|
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
|
||||||
|
assertMatcher(matcher).matches("/application/foo");
|
||||||
|
assertMatcher(matcher).matches("/application/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toAnyEndpointShouldNotMatchOtherPath() throws Exception {
|
||||||
|
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
|
||||||
|
assertMatcher(matcher).doesNotMatch("/application/baz");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toEndpointClassShouldMatchEndpointPath() throws Exception {
|
||||||
|
RequestMatcher matcher = EndpointRequest.to(FooEndpoint.class);
|
||||||
|
assertMatcher(matcher).matches("/application/foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toEndpointClassShouldNotMatchOtherPath() throws Exception {
|
||||||
|
RequestMatcher matcher = EndpointRequest.to(FooEndpoint.class);
|
||||||
|
assertMatcher(matcher).doesNotMatch("/application/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toEndpointIdShouldMatchEndpointPath() throws Exception {
|
||||||
|
RequestMatcher matcher = EndpointRequest.to("foo");
|
||||||
|
assertMatcher(matcher).matches("/application/foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toEndpointIdShouldNotMatchOtherPath() throws Exception {
|
||||||
|
RequestMatcher matcher = EndpointRequest.to("foo");
|
||||||
|
assertMatcher(matcher).doesNotMatch("/application/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void excludeByClassShouldNotMatchExcluded() throws Exception {
|
||||||
|
RequestMatcher matcher = EndpointRequest.toAnyEndpoint()
|
||||||
|
.excluding(FooEndpoint.class);
|
||||||
|
assertMatcher(matcher).doesNotMatch("/application/foo");
|
||||||
|
assertMatcher(matcher).matches("/application/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void excludeByIdShouldNotMatchExcluded() throws Exception {
|
||||||
|
RequestMatcher matcher = EndpointRequest.toAnyEndpoint().excluding("foo");
|
||||||
|
assertMatcher(matcher).doesNotMatch("/application/foo");
|
||||||
|
assertMatcher(matcher).matches("/application/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
|
||||||
|
return assertMatcher(matcher, new MockEndpointPathProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
|
||||||
|
EndpointPathProvider endpointPathProvider) {
|
||||||
|
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
||||||
|
context.registerBean(EndpointPathProvider.class, () -> endpointPathProvider);
|
||||||
|
return assertThat(new RequestMatcherAssert(context, matcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RequestMatcherAssert implements AssertDelegateTarget {
|
||||||
|
|
||||||
|
private final WebApplicationContext context;
|
||||||
|
|
||||||
|
private final RequestMatcher matcher;
|
||||||
|
|
||||||
|
RequestMatcherAssert(WebApplicationContext context, RequestMatcher matcher) {
|
||||||
|
this.context = context;
|
||||||
|
this.matcher = matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void matches(String path) {
|
||||||
|
matches(mockRequest(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void matches(HttpServletRequest request) {
|
||||||
|
assertThat(this.matcher.matches(request))
|
||||||
|
.as("Matches " + getRequestPath(request)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doesNotMatch(String path) {
|
||||||
|
doesNotMatch(mockRequest(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doesNotMatch(HttpServletRequest request) {
|
||||||
|
assertThat(this.matcher.matches(request))
|
||||||
|
.as("Does not match " + getRequestPath(request)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockHttpServletRequest mockRequest(String path) {
|
||||||
|
return mockRequest(null, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockHttpServletRequest mockRequest(String servletPath, String path) {
|
||||||
|
MockServletContext servletContext = new MockServletContext();
|
||||||
|
servletContext.setAttribute(
|
||||||
|
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
|
||||||
|
this.context);
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest(servletContext);
|
||||||
|
if (servletPath != null) {
|
||||||
|
request.setServletPath(servletPath);
|
||||||
|
}
|
||||||
|
request.setPathInfo(path);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRequestPath(HttpServletRequest request) {
|
||||||
|
String url = request.getServletPath();
|
||||||
|
if (request.getPathInfo() != null) {
|
||||||
|
url += request.getPathInfo();
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MockEndpointPathProvider implements EndpointPathProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getPaths() {
|
||||||
|
return Arrays.asList("/application/foo", "/application/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath(String id) {
|
||||||
|
if ("foo".equals(id)) {
|
||||||
|
return "/application/foo";
|
||||||
|
}
|
||||||
|
if ("bar".equals(id)) {
|
||||||
|
return "/application/bar";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Endpoint(id = "foo")
|
||||||
|
private static class FooEndpoint {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||||
|
import org.springframework.boot.security.ApplicationContextRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory that can be used to create a {@link RequestMatcher} for static resources in
|
||||||
|
* commonly used locations.
|
||||||
|
*
|
||||||
|
* @author Madhura Bhave
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public final class StaticResourceRequest {
|
||||||
|
|
||||||
|
private StaticResourceRequest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a matcher that includes all commonly used {@link Location Locations}. The
|
||||||
|
* {@link StaticResourceRequestMatcher#excluding(Location, Location...) excluding} method
|
||||||
|
* can be used to remove specific locations if required. For example:
|
||||||
|
* <pre class="code">
|
||||||
|
* StaticResourceRequest.toCommonLocations().excluding(Location.CSS)
|
||||||
|
* </pre>
|
||||||
|
* @return the configured {@link RequestMatcher}
|
||||||
|
*/
|
||||||
|
public static StaticResourceRequestMatcher toCommonLocations() {
|
||||||
|
return to(EnumSet.allOf(Location.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a matcher that includes the specified {@link Location Locations}. For
|
||||||
|
* example: <pre class="code">
|
||||||
|
* StaticResourceRequest.to(Location.CSS, Location.JAVA_SCRIPT)
|
||||||
|
* </pre>
|
||||||
|
* @param first the first location to include
|
||||||
|
* @param rest additional locations to include
|
||||||
|
* @return the configured {@link RequestMatcher}
|
||||||
|
*/
|
||||||
|
public static StaticResourceRequestMatcher to(Location first, Location... rest) {
|
||||||
|
return to(EnumSet.of(first, rest));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a matcher that includes the specified {@link Location Locations}. For
|
||||||
|
* example: <pre class="code">
|
||||||
|
* StaticResourceRequest.to(locations)
|
||||||
|
* </pre>
|
||||||
|
* @param locations the locations to include
|
||||||
|
* @return the configured {@link RequestMatcher}
|
||||||
|
*/
|
||||||
|
public static StaticResourceRequestMatcher to(Set<Location> locations) {
|
||||||
|
Assert.notNull(locations, "Locations must not be null");
|
||||||
|
return new StaticResourceRequestMatcher(new LinkedHashSet<>(locations));
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Location {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resources under {@code "/css"}.
|
||||||
|
*/
|
||||||
|
CSS("/css/**"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resources under {@code "/js"}.
|
||||||
|
*/
|
||||||
|
JAVA_SCRIPT("/js/**"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resources under {@code "/images"}.
|
||||||
|
*/
|
||||||
|
IMAGES("/images/**"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resources under {@code "/webjars"}.
|
||||||
|
*/
|
||||||
|
WEB_JARS("/webjars/**"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code "favicon.ico"} resource.
|
||||||
|
*/
|
||||||
|
FAVICON("/**/favicon.ico");
|
||||||
|
|
||||||
|
private String[] patterns;
|
||||||
|
|
||||||
|
Location(String... patterns) {
|
||||||
|
this.patterns = patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<String> getPatterns() {
|
||||||
|
return Arrays.stream(this.patterns);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request matcher used to match against resource {@link Location Locations}.
|
||||||
|
*/
|
||||||
|
public final static class StaticResourceRequestMatcher
|
||||||
|
extends ApplicationContextRequestMatcher<ServerProperties> {
|
||||||
|
|
||||||
|
private final Set<Location> locations;
|
||||||
|
|
||||||
|
private RequestMatcher delegate;
|
||||||
|
|
||||||
|
private StaticResourceRequestMatcher(Set<Location> locations) {
|
||||||
|
super(ServerProperties.class);
|
||||||
|
this.locations = locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link StaticResourceRequestMatcher} based on this one but excluding the
|
||||||
|
* specified locations.
|
||||||
|
* @param first the first location to exclude
|
||||||
|
* @param rest additional locations to exclude
|
||||||
|
* @return a new {@link StaticResourceRequestMatcher}
|
||||||
|
*/
|
||||||
|
public StaticResourceRequestMatcher excluding(Location first, Location... rest) {
|
||||||
|
return excluding(EnumSet.of(first, rest));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link StaticResourceRequestMatcher} based on this one but excluding the
|
||||||
|
* specified locations.
|
||||||
|
* @param locations the locations to exclude
|
||||||
|
* @return a new {@link StaticResourceRequestMatcher}
|
||||||
|
*/
|
||||||
|
public StaticResourceRequestMatcher excluding(Set<Location> locations) {
|
||||||
|
Assert.notNull(locations, "Locations must not be null");
|
||||||
|
Set<Location> subset = new LinkedHashSet<>(this.locations);
|
||||||
|
subset.removeAll(locations);
|
||||||
|
return new StaticResourceRequestMatcher(subset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initialized(ServerProperties serverProperties) {
|
||||||
|
this.delegate = new OrRequestMatcher(getDelegateMatchers(serverProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RequestMatcher> getDelegateMatchers(
|
||||||
|
ServerProperties serverProperties) {
|
||||||
|
return getPatterns(serverProperties).map(AntPathRequestMatcher::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<String> getPatterns(ServerProperties serverProperties) {
|
||||||
|
return this.locations.stream().flatMap(Location::getPatterns)
|
||||||
|
.map(serverProperties.getServlet()::getPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean matches(HttpServletRequest request, ServerProperties context) {
|
||||||
|
return this.delegate.matches(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.assertj.core.api.AssertDelegateTarget;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.security.StaticResourceRequest.Location;
|
||||||
|
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockServletContext;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link StaticResourceRequest}.
|
||||||
|
*
|
||||||
|
* @author Madhura Bhave
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class StaticResourceRequestTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCommonLocationsShouldMatchCommonLocations() throws Exception {
|
||||||
|
RequestMatcher matcher = StaticResourceRequest.toCommonLocations();
|
||||||
|
assertMatcher(matcher).matches("/css/file.css");
|
||||||
|
assertMatcher(matcher).matches("/js/file.js");
|
||||||
|
assertMatcher(matcher).matches("/images/file.css");
|
||||||
|
assertMatcher(matcher).matches("/webjars/file.css");
|
||||||
|
assertMatcher(matcher).matches("/foo/favicon.ico");
|
||||||
|
assertMatcher(matcher).doesNotMatch("/bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toCommonLocationsWithExcludeShouldNotMatchExcluded() throws Exception {
|
||||||
|
RequestMatcher matcher = StaticResourceRequest.toCommonLocations()
|
||||||
|
.excluding(Location.CSS);
|
||||||
|
assertMatcher(matcher).doesNotMatch("/css/file.css");
|
||||||
|
assertMatcher(matcher).matches("/js/file.js");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toLocationShouldMatchLocation() throws Exception {
|
||||||
|
RequestMatcher matcher = StaticResourceRequest.to(Location.CSS);
|
||||||
|
assertMatcher(matcher).matches("/css/file.css");
|
||||||
|
assertMatcher(matcher).doesNotMatch("/js/file.js");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toLocationWhenHasServletPathShouldMatchLocation() throws Exception {
|
||||||
|
ServerProperties serverProperties = new ServerProperties();
|
||||||
|
serverProperties.getServlet().setPath("/foo");
|
||||||
|
RequestMatcher matcher = StaticResourceRequest.to(Location.CSS);
|
||||||
|
assertMatcher(matcher, serverProperties).matches("/foo", "/css/file.css");
|
||||||
|
assertMatcher(matcher, serverProperties).doesNotMatch("/foo", "/js/file.js");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toLocationsFromSetWhenSetIsNullShouldThrowException() throws Exception {
|
||||||
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
|
this.thrown.expectMessage("Locations must not be null");
|
||||||
|
StaticResourceRequest.to((Set<Location>) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void excludeFromSetWhenSetIsNullShouldThrowException() throws Exception {
|
||||||
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
|
this.thrown.expectMessage("Locations must not be null");
|
||||||
|
StaticResourceRequest.toCommonLocations().excluding((Set<Location>) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
|
||||||
|
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
||||||
|
context.registerBean(ServerProperties.class);
|
||||||
|
return assertThat(new RequestMatcherAssert(context, matcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
|
||||||
|
ServerProperties serverProperties) {
|
||||||
|
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
||||||
|
context.registerBean(ServerProperties.class, () -> serverProperties);
|
||||||
|
return assertThat(new RequestMatcherAssert(context, matcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RequestMatcherAssert implements AssertDelegateTarget {
|
||||||
|
|
||||||
|
private final WebApplicationContext context;
|
||||||
|
|
||||||
|
private final RequestMatcher matcher;
|
||||||
|
|
||||||
|
RequestMatcherAssert(WebApplicationContext context, RequestMatcher matcher) {
|
||||||
|
this.context = context;
|
||||||
|
this.matcher = matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void matches(String path) {
|
||||||
|
matches(mockRequest(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void matches(String servletPath, String path) {
|
||||||
|
matches(mockRequest(servletPath, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void matches(HttpServletRequest request) {
|
||||||
|
assertThat(this.matcher.matches(request))
|
||||||
|
.as("Matches " + getRequestPath(request)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doesNotMatch(String path) {
|
||||||
|
doesNotMatch(mockRequest(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doesNotMatch(String servletPath, String path) {
|
||||||
|
doesNotMatch(mockRequest(servletPath, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doesNotMatch(HttpServletRequest request) {
|
||||||
|
assertThat(this.matcher.matches(request))
|
||||||
|
.as("Does not match " + getRequestPath(request)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockHttpServletRequest mockRequest(String path) {
|
||||||
|
return mockRequest(null, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockHttpServletRequest mockRequest(String servletPath, String path) {
|
||||||
|
MockServletContext servletContext = new MockServletContext();
|
||||||
|
servletContext.setAttribute(
|
||||||
|
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
|
||||||
|
this.context);
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest(servletContext);
|
||||||
|
if (servletPath != null) {
|
||||||
|
request.setServletPath(servletPath);
|
||||||
|
}
|
||||||
|
request.setPathInfo(path);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRequestPath(HttpServletRequest request) {
|
||||||
|
String url = request.getServletPath();
|
||||||
|
if (request.getPathInfo() != null) {
|
||||||
|
url += request.getPathInfo();
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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.security;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
|
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ApplicationContext} backed {@link RequestMatcher}. Can work directly with the
|
||||||
|
* {@link ApplicationContext}, obtain an existing bean or
|
||||||
|
* {@link AutowireCapableBeanFactory#createBean(Class, int, boolean) create a new bean}
|
||||||
|
* that is autowired in the usual way.
|
||||||
|
*
|
||||||
|
* @param <C> The type of the context that the match method actually needs to use. Can be
|
||||||
|
* an {@link ApplicationContext}, a class of an {@link ApplicationContext#getBean(Class)
|
||||||
|
* existing bean} or a custom type that will be
|
||||||
|
* {@link AutowireCapableBeanFactory#createBean(Class, int, boolean) created} on demand.
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public abstract class ApplicationContextRequestMatcher<C> implements RequestMatcher {
|
||||||
|
|
||||||
|
private final Class<? extends C> contextClass;
|
||||||
|
|
||||||
|
private volatile C context;
|
||||||
|
|
||||||
|
private Object contextLock = new Object();
|
||||||
|
|
||||||
|
public ApplicationContextRequestMatcher(Class<? extends C> contextClass) {
|
||||||
|
Assert.notNull(contextClass, "Context class must not be null");
|
||||||
|
this.contextClass = contextClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(HttpServletRequest request) {
|
||||||
|
return matches(request, getContext(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether the rule implemented by the strategy matches the supplied request.
|
||||||
|
* @param request the source request
|
||||||
|
* @param context the context instance
|
||||||
|
* @return if the request matches
|
||||||
|
*/
|
||||||
|
protected abstract boolean matches(HttpServletRequest request, C context);
|
||||||
|
|
||||||
|
private C getContext(HttpServletRequest request) {
|
||||||
|
if (this.context == null) {
|
||||||
|
synchronized (this.contextLock) {
|
||||||
|
this.context = createContext(request);
|
||||||
|
initialized(this.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called once the context has been initialized.
|
||||||
|
* @param context the initialized context
|
||||||
|
*/
|
||||||
|
protected void initialized(C context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private C createContext(HttpServletRequest request) {
|
||||||
|
WebApplicationContext context = WebApplicationContextUtils
|
||||||
|
.getRequiredWebApplicationContext(request.getServletContext());
|
||||||
|
if (this.contextClass.isInstance(context)) {
|
||||||
|
return (C) context;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return context.getBean(this.contextClass);
|
||||||
|
}
|
||||||
|
catch (NoSuchBeanDefinitionException ex) {
|
||||||
|
return (C) context.getAutowireCapableBeanFactory().createBean(
|
||||||
|
this.contextClass, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes and utilities for Spring Security.
|
||||||
|
*/
|
||||||
|
package org.springframework.boot.security;
|
@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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.security;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockServletContext;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ApplicationContextRequestMatcher}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class ApplicationContextRequestMatcherTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createWhenContextClassIsNullShouldThrowException() throws Exception {
|
||||||
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
|
this.thrown.expectMessage("Context class must not be null");
|
||||||
|
new TestApplicationContextRequestMatcher<>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchesWhenContextClassIsApplicationContextShouldProvideContext()
|
||||||
|
throws Exception {
|
||||||
|
StaticWebApplicationContext context = createWebApplicationContext();
|
||||||
|
assertThat(new TestApplicationContextRequestMatcher<>(ApplicationContext.class)
|
||||||
|
.callMatchesAndReturnProvidedContext(context)).isEqualTo(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchesWhenContextClassIsExistingBeanShouldProvideBean()
|
||||||
|
throws Exception {
|
||||||
|
StaticWebApplicationContext context = createWebApplicationContext();
|
||||||
|
context.registerSingleton("existingBean", ExistingBean.class);
|
||||||
|
assertThat(new TestApplicationContextRequestMatcher<>(ExistingBean.class)
|
||||||
|
.callMatchesAndReturnProvidedContext(context))
|
||||||
|
.isEqualTo(context.getBean(ExistingBean.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchesWhenContextClassIsNewBeanShouldProvideBean() throws Exception {
|
||||||
|
StaticWebApplicationContext context = createWebApplicationContext();
|
||||||
|
context.registerSingleton("existingBean", ExistingBean.class);
|
||||||
|
assertThat(new TestApplicationContextRequestMatcher<>(NewBean.class)
|
||||||
|
.callMatchesAndReturnProvidedContext(context).getBean())
|
||||||
|
.isEqualTo(context.getBean(ExistingBean.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private StaticWebApplicationContext createWebApplicationContext() {
|
||||||
|
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
||||||
|
MockServletContext servletContext = new MockServletContext();
|
||||||
|
context.setServletContext(servletContext);
|
||||||
|
servletContext.setAttribute(
|
||||||
|
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ExistingBean {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class NewBean {
|
||||||
|
|
||||||
|
private final ExistingBean bean;
|
||||||
|
|
||||||
|
NewBean(ExistingBean bean) {
|
||||||
|
this.bean = bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExistingBean getBean() {
|
||||||
|
return this.bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestApplicationContextRequestMatcher<C>
|
||||||
|
extends ApplicationContextRequestMatcher<C> {
|
||||||
|
|
||||||
|
private C provdedContext;
|
||||||
|
|
||||||
|
TestApplicationContextRequestMatcher(Class<? extends C> context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public C callMatchesAndReturnProvidedContext(WebApplicationContext context) {
|
||||||
|
return callMatchesAndReturnProvidedContext(
|
||||||
|
new MockHttpServletRequest(context.getServletContext()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public C callMatchesAndReturnProvidedContext(HttpServletRequest request) {
|
||||||
|
matches(request);
|
||||||
|
return getProvdedContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean matches(HttpServletRequest request, C context) {
|
||||||
|
this.provdedContext = context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public C getProvdedContext() {
|
||||||
|
return this.provdedContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue