Commit 1283bc05 authored by Phillip Webb's avatar Phillip Webb

Merge branch '2.1.x'

Closes gh-18021
parents 9e640cf6 5938ca78
...@@ -37,6 +37,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint; ...@@ -37,6 +37,7 @@ 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.security.servlet.RequestMatcherProvider;
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher; import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
...@@ -45,7 +46,6 @@ import org.springframework.security.web.util.matcher.RequestMatcher; ...@@ -45,7 +46,6 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
/** /**
* Factory that can be used to create a {@link RequestMatcher} for actuator endpoint * Factory that can be used to create a {@link RequestMatcher} for actuator endpoint
...@@ -128,6 +128,13 @@ public final class EndpointRequest { ...@@ -128,6 +128,13 @@ public final class EndpointRequest {
super(WebApplicationContext.class); super(WebApplicationContext.class);
} }
@Override
protected boolean ignoreApplicationContext(WebApplicationContext applicationContext) {
ManagementPortType type = ManagementPortType.get(applicationContext.getEnvironment());
return type == ManagementPortType.DIFFERENT
&& WebServerApplicationContext.hasServerNamespace(applicationContext, "management");
}
@Override @Override
protected final void initialized(Supplier<WebApplicationContext> context) { protected final void initialized(Supplier<WebApplicationContext> context) {
this.delegate = createDelegate(context.get()); this.delegate = createDelegate(context.get());
...@@ -135,17 +142,6 @@ public final class EndpointRequest { ...@@ -135,17 +142,6 @@ public final class EndpointRequest {
@Override @Override
protected final boolean matches(HttpServletRequest request, Supplier<WebApplicationContext> context) { protected final boolean matches(HttpServletRequest request, Supplier<WebApplicationContext> context) {
WebApplicationContext applicationContext = WebApplicationContextUtils
.getRequiredWebApplicationContext(request.getServletContext());
if (ManagementPortType.get(applicationContext.getEnvironment()) == ManagementPortType.DIFFERENT) {
if (applicationContext.getParent() == null) {
return false;
}
String managementContextId = applicationContext.getParent().getId() + ":management";
if (!managementContextId.equals(applicationContext.getId())) {
return false;
}
}
return this.delegate.matches(request); return this.delegate.matches(request);
} }
......
...@@ -23,8 +23,10 @@ import javax.servlet.http.HttpServletRequest; ...@@ -23,8 +23,10 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.h2.H2ConsoleProperties; import org.springframework.boot.autoconfigure.h2.H2ConsoleProperties;
import org.springframework.boot.autoconfigure.security.StaticResourceLocation; import org.springframework.boot.autoconfigure.security.StaticResourceLocation;
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher; import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.context.WebApplicationContext;
/** /**
* Factory that can be used to create a {@link RequestMatcher} for commonly used paths. * Factory that can be used to create a {@link RequestMatcher} for commonly used paths.
...@@ -69,6 +71,11 @@ public final class PathRequest { ...@@ -69,6 +71,11 @@ public final class PathRequest {
super(H2ConsoleProperties.class); super(H2ConsoleProperties.class);
} }
@Override
protected boolean ignoreApplicationContext(WebApplicationContext applicationContext) {
return WebServerApplicationContext.hasServerNamespace(applicationContext, "management");
}
@Override @Override
protected void initialized(Supplier<H2ConsoleProperties> h2ConsoleProperties) { protected void initialized(Supplier<H2ConsoleProperties> h2ConsoleProperties) {
this.delegate = new AntPathRequestMatcher(h2ConsoleProperties.get().getPath() + "/**"); this.delegate = new AntPathRequestMatcher(h2ConsoleProperties.get().getPath() + "/**");
......
...@@ -29,10 +29,12 @@ import javax.servlet.http.HttpServletRequest; ...@@ -29,10 +29,12 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.security.StaticResourceLocation; import org.springframework.boot.autoconfigure.security.StaticResourceLocation;
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.boot.web.context.WebServerApplicationContext;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
/** /**
* Used to create a {@link RequestMatcher} for static resources in commonly used * Used to create a {@link RequestMatcher} for static resources in commonly used
...@@ -144,6 +146,11 @@ public final class StaticResourceRequest { ...@@ -144,6 +146,11 @@ public final class StaticResourceRequest {
.map(dispatcherServletPath::getRelativePath); .map(dispatcherServletPath::getRelativePath);
} }
@Override
protected boolean ignoreApplicationContext(WebApplicationContext applicationContext) {
return WebServerApplicationContext.hasServerNamespace(applicationContext, "management");
}
@Override @Override
protected boolean matches(HttpServletRequest request, Supplier<DispatcherServletPath> context) { protected boolean matches(HttpServletRequest request, Supplier<DispatcherServletPath> context) {
return this.delegate.matches(request); return this.delegate.matches(request);
......
...@@ -27,7 +27,6 @@ import org.springframework.mock.web.MockHttpServletRequest; ...@@ -27,7 +27,6 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -51,8 +50,20 @@ class PathRequestTests { ...@@ -51,8 +50,20 @@ class PathRequestTests {
assertMatcher(matcher).doesNotMatch("/js/file.js"); assertMatcher(matcher).doesNotMatch("/js/file.js");
} }
@Test
void toH2ConsoleWhenManagementContextShouldNeverMatch() {
RequestMatcher matcher = PathRequest.toH2Console();
assertMatcher(matcher, "management").doesNotMatch("/h2-console");
assertMatcher(matcher, "management").doesNotMatch("/h2-console/subpath");
assertMatcher(matcher, "management").doesNotMatch("/js/file.js");
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) { private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
StaticWebApplicationContext context = new StaticWebApplicationContext(); return assertMatcher(matcher, null);
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String serverNamespace) {
TestWebApplicationContext context = new TestWebApplicationContext(serverNamespace);
context.registerBean(ServerProperties.class); context.registerBean(ServerProperties.class);
context.registerBean(H2ConsoleProperties.class); context.registerBean(H2ConsoleProperties.class);
return assertThat(new RequestMatcherAssert(context, matcher)); return assertThat(new RequestMatcherAssert(context, matcher));
......
...@@ -27,7 +27,6 @@ import org.springframework.mock.web.MockHttpServletRequest; ...@@ -27,7 +27,6 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
...@@ -53,6 +52,16 @@ class StaticResourceRequestTests { ...@@ -53,6 +52,16 @@ class StaticResourceRequestTests {
assertMatcher(matcher).doesNotMatch("/bar"); assertMatcher(matcher).doesNotMatch("/bar");
} }
@Test
void atCommonLocationsWhenManagementContextShouldNeverMatch() {
RequestMatcher matcher = this.resourceRequest.atCommonLocations();
assertMatcher(matcher, "management").doesNotMatch("/css/file.css");
assertMatcher(matcher, "management").doesNotMatch("/js/file.js");
assertMatcher(matcher, "management").doesNotMatch("/images/file.css");
assertMatcher(matcher, "management").doesNotMatch("/webjars/file.css");
assertMatcher(matcher, "management").doesNotMatch("/foo/favicon.ico");
}
@Test @Test
void atCommonLocationsWithExcludeShouldNotMatchExcluded() { void atCommonLocationsWithExcludeShouldNotMatchExcluded() {
RequestMatcher matcher = this.resourceRequest.atCommonLocations().excluding(StaticResourceLocation.CSS); RequestMatcher matcher = this.resourceRequest.atCommonLocations().excluding(StaticResourceLocation.CSS);
...@@ -70,8 +79,8 @@ class StaticResourceRequestTests { ...@@ -70,8 +79,8 @@ class StaticResourceRequestTests {
@Test @Test
void atLocationWhenHasServletPathShouldMatchLocation() { void atLocationWhenHasServletPathShouldMatchLocation() {
RequestMatcher matcher = this.resourceRequest.at(StaticResourceLocation.CSS); RequestMatcher matcher = this.resourceRequest.at(StaticResourceLocation.CSS);
assertMatcher(matcher, "/foo").matches("/foo", "/css/file.css"); assertMatcher(matcher, null, "/foo").matches("/foo", "/css/file.css");
assertMatcher(matcher, "/foo").doesNotMatch("/foo", "/js/file.js"); assertMatcher(matcher, null, "/foo").doesNotMatch("/foo", "/js/file.js");
} }
@Test @Test
...@@ -87,15 +96,16 @@ class StaticResourceRequestTests { ...@@ -87,15 +96,16 @@ class StaticResourceRequestTests {
} }
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) { private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
DispatcherServletPath dispatcherServletPath = () -> ""; return assertMatcher(matcher, null, "");
StaticWebApplicationContext context = new StaticWebApplicationContext(); }
context.registerBean(DispatcherServletPath.class, () -> dispatcherServletPath);
return assertThat(new RequestMatcherAssert(context, matcher)); private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String serverNamespace) {
return assertMatcher(matcher, serverNamespace, "");
} }
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String path) { private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String serverNamespace, String path) {
DispatcherServletPath dispatcherServletPath = () -> path; DispatcherServletPath dispatcherServletPath = () -> path;
StaticWebApplicationContext context = new StaticWebApplicationContext(); TestWebApplicationContext context = new TestWebApplicationContext(serverNamespace);
context.registerBean(DispatcherServletPath.class, () -> dispatcherServletPath); context.registerBean(DispatcherServletPath.class, () -> dispatcherServletPath);
return assertThat(new RequestMatcherAssert(context, matcher)); return assertThat(new RequestMatcherAssert(context, matcher));
} }
......
/*
* Copyright 2012-2019 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
*
* https://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.web.context.WebServerApplicationContext;
import org.springframework.boot.web.server.WebServer;
import org.springframework.web.context.support.StaticWebApplicationContext;
/**
* Test {@link StaticWebApplicationContext} that also implements
* {@link WebServerApplicationContext}.
*
* @author Phillip Webb
*/
class TestWebApplicationContext extends StaticWebApplicationContext implements WebServerApplicationContext {
private final String serverNamespace;
TestWebApplicationContext(String serverNamespace) {
this.serverNamespace = serverNamespace;
}
@Override
public WebServer getWebServer() {
return null;
}
@Override
public String getServerNamespace() {
return this.serverNamespace;
}
}
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.security.servlet; package org.springframework.boot.security.servlet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
...@@ -43,9 +44,7 @@ public abstract class ApplicationContextRequestMatcher<C> implements RequestMatc ...@@ -43,9 +44,7 @@ public abstract class ApplicationContextRequestMatcher<C> implements RequestMatc
private final Class<? extends C> contextClass; private final Class<? extends C> contextClass;
private volatile Supplier<C> context; private final AtomicBoolean initialized = new AtomicBoolean(false);
private final Object contextLock = new Object();
public ApplicationContextRequestMatcher(Class<? extends C> contextClass) { public ApplicationContextRequestMatcher(Class<? extends C> contextClass) {
Assert.notNull(contextClass, "Context class must not be null"); Assert.notNull(contextClass, "Context class must not be null");
...@@ -54,45 +53,56 @@ public abstract class ApplicationContextRequestMatcher<C> implements RequestMatc ...@@ -54,45 +53,56 @@ public abstract class ApplicationContextRequestMatcher<C> implements RequestMatc
@Override @Override
public final boolean matches(HttpServletRequest request) { public final boolean matches(HttpServletRequest request) {
return matches(request, getContext(request)); WebApplicationContext webApplicationContext = WebApplicationContextUtils
.getRequiredWebApplicationContext(request.getServletContext());
if (ignoreApplicationContext(webApplicationContext)) {
return false;
}
Supplier<C> context = () -> getContext(webApplicationContext);
if (this.initialized.compareAndSet(false, true)) {
initialized(context);
}
return matches(request, context);
}
@SuppressWarnings("unchecked")
private C getContext(WebApplicationContext webApplicationContext) {
if (this.contextClass.isInstance(webApplicationContext)) {
return (C) webApplicationContext;
}
return webApplicationContext.getBean(this.contextClass);
} }
/** /**
* Decides whether the rule implemented by the strategy matches the supplied request. * Returns if the {@link WebApplicationContext} should be ignored and not used for
* @param request the source request * matching. If this method returns {@code true} then the context will not be used and
* @param context a supplier for the initialized context (may throw an exception) * the {@link #matches(HttpServletRequest) matches} method will return {@code false}.
* @return if the request matches * @param webApplicationContext the candidate web application context
* @return if the application context should be ignored
* @since 2.1.8
*/ */
protected abstract boolean matches(HttpServletRequest request, Supplier<C> context); protected boolean ignoreApplicationContext(WebApplicationContext webApplicationContext) {
return false;
private Supplier<C> getContext(HttpServletRequest request) {
if (this.context == null) {
synchronized (this.contextLock) {
if (this.context == null) {
Supplier<C> createdContext = createContext(request);
initialized(createdContext);
this.context = createdContext;
}
}
}
return this.context;
} }
/** /**
* Called once the context has been initialized. * Method that can be implemented by subclasses that wish to initialize items the
* first time that the matcher is called. This method will be called only once and
* only if {@link #ignoreApplicationContext(WebApplicationContext)} returns
* {@code true}. Note that the supplied context will be based on the
* <strong>first</strong> request sent to the matcher.
* @param context a supplier for the initialized context (may throw an exception) * @param context a supplier for the initialized context (may throw an exception)
* @see #ignoreApplicationContext(WebApplicationContext)
*/ */
protected void initialized(Supplier<C> context) { protected void initialized(Supplier<C> context) {
} }
@SuppressWarnings("unchecked") /**
private Supplier<C> createContext(HttpServletRequest request) { * Decides whether the rule implemented by the strategy matches the supplied request.
WebApplicationContext context = WebApplicationContextUtils * @param request the source request
.getRequiredWebApplicationContext(request.getServletContext()); * @param context a supplier for the initialized context (may throw an exception)
if (this.contextClass.isInstance(context)) { * @return if the request matches
return () -> (C) context; */
} protected abstract boolean matches(HttpServletRequest request, Supplier<C> context);
return () -> context.getBean(this.contextClass);
}
} }
...@@ -18,6 +18,7 @@ package org.springframework.boot.web.context; ...@@ -18,6 +18,7 @@ package org.springframework.boot.web.context;
import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.util.ObjectUtils;
/** /**
* Interface to be implemented by {@link ApplicationContext application contexts} that * Interface to be implemented by {@link ApplicationContext application contexts} that
...@@ -44,4 +45,17 @@ public interface WebServerApplicationContext extends ApplicationContext { ...@@ -44,4 +45,17 @@ public interface WebServerApplicationContext extends ApplicationContext {
*/ */
String getServerNamespace(); String getServerNamespace();
/**
* Returns {@code true} if the specified context is a
* {@link WebServerApplicationContext} with a matching server namespace.
* @param context the context to check
* @param serverNamespace the server namespace to match against
* @return {@code true} if the server namespace of the context matches
* @since 2.1.8
*/
static boolean hasServerNamespace(ApplicationContext context, String serverNamespace) {
return (context instanceof WebServerApplicationContext) && ObjectUtils
.nullSafeEquals(((WebServerApplicationContext) context).getServerNamespace(), serverNamespace);
}
} }
...@@ -69,6 +69,42 @@ class ApplicationContextRequestMatcherTests { ...@@ -69,6 +69,42 @@ class ApplicationContextRequestMatcherTests {
assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(supplier::get); assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(supplier::get);
} }
@Test // gh-18012
void machesWhenCalledWithDifferentApplicationContextDoesNotCache() {
StaticWebApplicationContext context1 = createWebApplicationContext();
StaticWebApplicationContext context2 = createWebApplicationContext();
TestApplicationContextRequestMatcher<ApplicationContext> matcher = new TestApplicationContextRequestMatcher<>(
ApplicationContext.class);
assertThat(matcher.callMatchesAndReturnProvidedContext(context1).get()).isEqualTo(context1);
assertThat(matcher.callMatchesAndReturnProvidedContext(context2).get()).isEqualTo(context2);
}
@Test
void initializeAndMatchesAreNotCalledIfContextIsIgnored() {
StaticWebApplicationContext context = createWebApplicationContext();
TestApplicationContextRequestMatcher<ApplicationContext> matcher = new TestApplicationContextRequestMatcher<ApplicationContext>(
ApplicationContext.class) {
@Override
protected boolean ignoreApplicationContext(WebApplicationContext webApplicationContext) {
return true;
}
@Override
protected void initialized(Supplier<ApplicationContext> context) {
throw new IllegalStateException();
}
@Override
protected boolean matches(HttpServletRequest request, Supplier<ApplicationContext> context) {
throw new IllegalStateException();
}
};
MockHttpServletRequest request = new MockHttpServletRequest(context.getServletContext());
assertThat(matcher.matches(request)).isFalse();
}
private StaticWebApplicationContext createWebApplicationContext() { private StaticWebApplicationContext createWebApplicationContext() {
StaticWebApplicationContext context = new StaticWebApplicationContext(); StaticWebApplicationContext context = new StaticWebApplicationContext();
MockServletContext servletContext = new MockServletContext(); MockServletContext servletContext = new MockServletContext();
......
/*
* Copyright 2012-2019 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
*
* https://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.web.context;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link WebServerApplicationContext}.
*
* @author Phillip Webb
*/
public class WebServerApplicationContextTests {
@Test
public void hasServerNamespaceWhenContextIsNotWebServerApplicationContextReturnsFalse() {
ApplicationContext context = mock(ApplicationContext.class);
assertThat(WebServerApplicationContext.hasServerNamespace(context, "test")).isFalse();
}
@Test
public void hasServerNamespaceWhenContextIsWebServerApplicationContextAndNamespaceDoesNotMatchReturnsFalse() {
ApplicationContext context = mock(WebServerApplicationContext.class);
assertThat(WebServerApplicationContext.hasServerNamespace(context, "test")).isFalse();
}
@Test
public void hasServerNamespaceWhenContextIsWebServerApplicationContextAndNamespaceMatchesReturnsTrue() {
WebServerApplicationContext context = mock(WebServerApplicationContext.class);
given(context.getServerNamespace()).willReturn("test");
assertThat(WebServerApplicationContext.hasServerNamespace(context, "test")).isTrue();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment