diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java
new file mode 100644
index 00000000000..f81191ef627
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.reactive;
+
+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 reactor.core.publisher.Mono;
+
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.boot.security.reactive.ApplicationContextServerWebExchangeMatcher;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
+import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Factory that can be used to create a {@link ServerWebExchangeMatcher} for actuator endpoint
+ * locations.
+ *
+ * @author Madhura Bhave
+ * @since 2.0.0
+ */
+public final class EndpointRequest {
+
+ private EndpointRequest() {
+ }
+
+ /**
+ * Returns a matcher that includes all {@link Endpoint actuator endpoints}. The
+ * {@link EndpointServerWebExchangeMatcher#excluding(Class...) excluding} method can be used to
+ * further remove specific endpoints if required. For example:
+ * EndpointServerWebExchangeMatcher.toAnyEndpoint().excluding(ShutdownEndpoint.class)
+ *
+ * @return the configured {@link ServerWebExchangeMatcher}
+ */
+ public static EndpointServerWebExchangeMatcher toAnyEndpoint() {
+ return new EndpointServerWebExchangeMatcher();
+ }
+
+ /**
+ * Returns a matcher that includes the specified {@link Endpoint actuator endpoints}.
+ * For example:
+ * EndpointRequest.to(ShutdownEndpoint.class, HealthEndpoint.class)
+ *
+ * @param endpoints the endpoints to include
+ * @return the configured {@link ServerWebExchangeMatcher}
+ */
+ public static EndpointServerWebExchangeMatcher to(Class>... endpoints) {
+ return new EndpointServerWebExchangeMatcher(endpoints);
+ }
+
+ /**
+ * Returns a matcher that includes the specified {@link Endpoint actuator endpoints}.
+ * For example:
+ * EndpointRequest.to("shutdown", "health")
+ *
+ * @param endpoints the endpoints to include
+ * @return the configured {@link ServerWebExchangeMatcher}
+ */
+ public static EndpointServerWebExchangeMatcher to(String... endpoints) {
+ return new EndpointServerWebExchangeMatcher(endpoints);
+ }
+
+ /**
+ * The {@link ServerWebExchangeMatcher} used to match against {@link Endpoint actuator endpoints}.
+ */
+ public final static class EndpointServerWebExchangeMatcher
+ extends ApplicationContextServerWebExchangeMatcher {
+
+ private final List includes;
+
+ private final List excludes;
+
+ private ServerWebExchangeMatcher delegate;
+
+ private EndpointServerWebExchangeMatcher() {
+ super(EndpointPathProvider.class);
+ this.includes = Collections.emptyList();
+ this.excludes = Collections.emptyList();
+ }
+
+ private EndpointServerWebExchangeMatcher(Class>[] endpoints) {
+ super(EndpointPathProvider.class);
+ this.includes = Arrays.asList((Object[]) endpoints);
+ this.excludes = Collections.emptyList();
+ }
+
+ private EndpointServerWebExchangeMatcher(String[] endpoints) {
+ super(EndpointPathProvider.class);
+ this.includes = Arrays.asList((Object[]) endpoints);
+ this.excludes = Collections.emptyList();
+ }
+
+ private EndpointServerWebExchangeMatcher(List includes, List excludes) {
+ super(EndpointPathProvider.class);
+ this.includes = includes;
+ this.excludes = excludes;
+ }
+
+ EndpointServerWebExchangeMatcher excluding(Class>... endpoints) {
+ List excludes = new ArrayList<>(this.excludes);
+ excludes.addAll(Arrays.asList((Object[]) endpoints));
+ return new EndpointServerWebExchangeMatcher(this.includes, excludes);
+ }
+
+ EndpointServerWebExchangeMatcher excluding(String... endpoints) {
+ List excludes = new ArrayList<>(this.excludes);
+ excludes.addAll(Arrays.asList((Object[]) endpoints));
+ return new EndpointServerWebExchangeMatcher(this.includes, excludes);
+ }
+
+ @Override
+ protected void initialized(EndpointPathProvider endpointPathProvider) {
+ Set 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 OrServerWebExchangeMatcher(getDelegateMatchers(paths));
+ }
+
+ private Stream streamPaths(List 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 getDelegateMatchers(Set paths) {
+ return paths.stream().map((path) -> new PathPatternParserServerWebExchangeMatcher(path + "/**"))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ protected Mono matches(ServerWebExchange exchange,
+ EndpointPathProvider context) {
+ return this.delegate.matches(exchange);
+ }
+
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/EndpointRequest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java
similarity index 94%
rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/EndpointRequest.java
rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java
index 5ddc89bcc51..d17f60019bb 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/EndpointRequest.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.actuate.autoconfigure.security;
+package org.springframework.boot.actuate.autoconfigure.security.servlet;
import java.util.ArrayList;
import java.util.Arrays;
@@ -28,10 +28,9 @@ 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.boot.security.servlet.ApplicationContextRequestMatcher;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
@@ -172,16 +171,6 @@ public final class EndpointRequest {
.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) {
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java
new file mode 100644
index 00000000000..ddf69001848
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java
@@ -0,0 +1,190 @@
+/*
+ * 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.reactive;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.assertj.core.api.AssertDelegateTarget;
+import org.junit.Test;
+
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
+import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
+import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebHandler;
+import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link EndpointRequest}.
+ *
+ * @author Madhura Bhave
+ */
+public class EndpointRequestTests {
+
+ @Test
+ public void toAnyEndpointShouldMatchEndpointPath() {
+ ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint();
+ assertMatcher(matcher).matches("/actuator/foo");
+ assertMatcher(matcher).matches("/actuator/bar");
+ }
+
+ @Test
+ public void toAnyEndpointShouldNotMatchOtherPath() {
+ ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint();
+ assertMatcher(matcher).doesNotMatch("/actuator/baz");
+ }
+
+ @Test
+ public void toEndpointClassShouldMatchEndpointPath() {
+ ServerWebExchangeMatcher matcher = EndpointRequest.to(FooEndpoint.class);
+ assertMatcher(matcher).matches("/actuator/foo");
+ }
+
+ @Test
+ public void toEndpointClassShouldNotMatchOtherPath() {
+ ServerWebExchangeMatcher matcher = EndpointRequest.to(FooEndpoint.class);
+ assertMatcher(matcher).doesNotMatch("/actuator/bar");
+ }
+
+ @Test
+ public void toEndpointIdShouldMatchEndpointPath() {
+ ServerWebExchangeMatcher matcher = EndpointRequest.to("foo");
+ assertMatcher(matcher).matches("/actuator/foo");
+ }
+
+ @Test
+ public void toEndpointIdShouldNotMatchOtherPath() {
+ ServerWebExchangeMatcher matcher = EndpointRequest.to("foo");
+ assertMatcher(matcher).doesNotMatch("/actuator/bar");
+ }
+
+ @Test
+ public void excludeByClassShouldNotMatchExcluded() {
+ ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint()
+ .excluding(FooEndpoint.class);
+ assertMatcher(matcher).doesNotMatch("/actuator/foo");
+ assertMatcher(matcher).matches("/actuator/bar");
+ }
+
+ @Test
+ public void excludeByIdShouldNotMatchExcluded() {
+ ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint().excluding("foo");
+ assertMatcher(matcher).doesNotMatch("/actuator/foo");
+ assertMatcher(matcher).matches("/actuator/bar");
+ }
+
+ private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher) {
+ return assertMatcher(matcher, new MockEndpointPathProvider());
+ }
+
+ private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher,
+ EndpointPathProvider endpointPathProvider) {
+ StaticApplicationContext context = new StaticApplicationContext();
+ context.registerBean(EndpointPathProvider.class, () -> endpointPathProvider);
+ return assertThat(new RequestMatcherAssert(context, matcher));
+ }
+
+ private static class RequestMatcherAssert implements AssertDelegateTarget {
+
+ private final StaticApplicationContext context;
+
+ private final ServerWebExchangeMatcher matcher;
+
+ RequestMatcherAssert(StaticApplicationContext context, ServerWebExchangeMatcher matcher) {
+ this.context = context;
+ this.matcher = matcher;
+ }
+
+ void matches(String path) {
+ ServerWebExchange exchange = webHandler().createExchange(MockServerHttpRequest.get(path).build(), new MockServerHttpResponse());
+ matches(exchange);
+ }
+
+ private void matches(ServerWebExchange exchange) {
+ assertThat(this.matcher.matches(exchange).block().isMatch())
+ .as("Matches " + getRequestPath(exchange)).isTrue();
+ }
+
+ void doesNotMatch(String path) {
+ ServerWebExchange exchange = webHandler().createExchange(MockServerHttpRequest.get(path).build(), new MockServerHttpResponse());
+ doesNotMatch(exchange);
+ }
+
+ private void doesNotMatch(ServerWebExchange exchange) {
+ assertThat(this.matcher.matches(exchange).block().isMatch())
+ .as("Does not match " + getRequestPath(exchange)).isFalse();
+ }
+
+ private TestHttpWebHandlerAdapter webHandler() {
+ TestHttpWebHandlerAdapter adapter = new TestHttpWebHandlerAdapter(mock(WebHandler.class));
+ adapter.setApplicationContext(this.context);
+ return adapter;
+ }
+
+ private String getRequestPath(ServerWebExchange exchange) {
+ return exchange.getRequest().getPath().toString();
+ }
+
+ }
+
+ private static class TestHttpWebHandlerAdapter extends HttpWebHandlerAdapter {
+
+ TestHttpWebHandlerAdapter(WebHandler delegate) {
+ super(delegate);
+ }
+
+ @Override
+ protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttpResponse response) {
+ return super.createExchange(request, response);
+ }
+
+ }
+
+ private static class MockEndpointPathProvider implements EndpointPathProvider {
+
+ @Override
+ public List getPaths() {
+ return Arrays.asList("/actuator/foo", "/actuator/bar");
+ }
+
+ @Override
+ public String getPath(String id) {
+ if ("foo".equals(id)) {
+ return "/actuator/foo";
+ }
+ if ("bar".equals(id)) {
+ return "/actuator/bar";
+ }
+ return null;
+ }
+
+ }
+
+ @Endpoint(id = "foo")
+ private static class FooEndpoint {
+
+ }
+}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/EndpointRequestTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java
similarity index 98%
rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/EndpointRequestTests.java
rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java
index e45f3ab46d9..026e9214b63 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/EndpointRequestTests.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.actuate.autoconfigure.security;
+package org.springframework.boot.actuate.autoconfigure.security.servlet;
import java.util.Arrays;
import java.util.List;
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/StaticResourceRequest.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/StaticResourceRequest.java
index cb592550199..ef37eb852db 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/StaticResourceRequest.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/StaticResourceRequest.java
@@ -27,7 +27,7 @@ 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.boot.security.servlet.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;
diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
index 1c4c6b8eeb7..238c36f8e08 100644
--- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
+++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
@@ -2882,6 +2882,17 @@ messages. Otherwise, the default password is not printed.
You can change the username and password by providing a `spring.security.user.name` and
`spring.security.user.password`.
+The basic features you get by default in a web application are:
+
+* A `UserDetailsService` (or `ReactiveUserDetailsService` in case of a WebFlux application)
+bean with in-memory store and a single user with a generated password (see
+{dc-spring-boot}/autoconfigure/security/SecurityProperties.User.html[`SecurityProperties.User`]
+for the properties of the user).
+* Form-based login or HTTP Basic security (depending on Content-Type) for the entire
+application (including actuator endpoints if actuator is on the classpath).
+
+== MVC Security
+
The default security configuration is implemented in `SecurityAutoConfiguration` and in
the classes imported from there (`SpringBootWebSecurityConfiguration` for web security
and `AuthenticationManagerConfiguration` for authentication configuration, which is also
@@ -2894,15 +2905,6 @@ To also switch off the authentication manager configuration, you can add a bean
There are several secure applications in the {github-code}/spring-boot-samples/[Spring
Boot samples] to get you started with common use cases.
-The basic features you get by default in a web application are:
-
-* A `UserDetailsService` bean with in-memory store and a single user with a generated
-password (see
-{dc-spring-boot}/autoconfigure/security/SecurityProperties.User.html[`SecurityProperties.User`]
-for the properties of the user).
-* Form-based login or HTTP Basic security (depending on Content-Type) for the entire
-application (including actuator endpoints if actuator is on the classpath).
-
Access rules can be overridden by adding a custom `WebSecurityConfigurerAdapter`. Spring
Boot provides convenience methods that can be used to override access rules for actuator
endpoints and static resources. `EndpointRequest` can be used to create a `RequestMatcher`
@@ -2910,7 +2912,22 @@ that is based on the `management.endpoints.web.base-path` property.
`StaticResourceRequest` can be used to create a `RequestMatcher` for static resources in
commonly used locations.
+== WebFlux Security
+The default security configuration is implemented in `ReactiveSecurityAutoConfiguration` and in
+the classes imported from there (`WebFluxSecurityConfiguration` for web security
+and `ReactiveAuthenticationManagerConfiguration` for authentication configuration, which is also
+relevant in non-web applications). To switch off the default web application security
+configuration completely, you can add a bean of type `WebFilterChainProxy` (doing
+so does not disable the authentication manager configuration or Actuator's security).
+
+To also switch off the authentication manager configuration, you can add a bean of type
+`ReactiveUserDetailsService` or `ReactiveAuthenticationManager`.
+
+Access rules can be configured by adding a custom `SecurityWebFilterChain`. Spring
+Boot provides convenience methods that can be used to override access rules for actuator
+endpoints and static resources. `EndpointRequest` can be used to create a `ServerWebExchangeMatcher`
+that is based on the `management.endpoints.web.base-path` property.
[[boot-features-security-oauth2]]
=== OAuth2
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/security/reactive/ApplicationContextServerWebExchangeMatcher.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/security/reactive/ApplicationContextServerWebExchangeMatcher.java
new file mode 100644
index 00000000000..ef785f94c32
--- /dev/null
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/security/reactive/ApplicationContextServerWebExchangeMatcher.java
@@ -0,0 +1,104 @@
+/*
+ * 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.security.reactive;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * {@link ApplicationContext} backed {@link ServerWebExchangeMatcher}. 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 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 Madhura Bhave
+ * @since 2.0.0
+ */
+public abstract class ApplicationContextServerWebExchangeMatcher implements ServerWebExchangeMatcher {
+
+ private final Class extends C> contextClass;
+
+ private C context;
+
+ private Object contextLock = new Object();
+
+ public ApplicationContextServerWebExchangeMatcher(Class extends C> contextClass) {
+ Assert.notNull(contextClass, "Context class must not be null");
+ this.contextClass = contextClass;
+ }
+
+ @Override
+ public final Mono matches(ServerWebExchange exchange) {
+ return matches(exchange, getContext(exchange));
+ }
+
+ /**
+ * Decides whether the rule implemented by the strategy matches the supplied exchange.
+ * @param exchange the source exchange
+ * @param context the context instance
+ * @return if the exchange matches
+ */
+ protected abstract Mono matches(ServerWebExchange exchange, C context);
+
+ protected C getContext(ServerWebExchange exchange) {
+ if (this.context == null) {
+ synchronized (this.contextLock) {
+ this.context = createContext(exchange);
+ 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(ServerWebExchange exchange) {
+ ApplicationContext context = exchange.getApplicationContext();
+ if (context == null) {
+ throw new IllegalStateException("No WebApplicationContext found.");
+ }
+ 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);
+ }
+ }
+
+}
+
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/security/ApplicationContextRequestMatcher.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/security/servlet/ApplicationContextRequestMatcher.java
similarity index 96%
rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/security/ApplicationContextRequestMatcher.java
rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/security/servlet/ApplicationContextRequestMatcher.java
index a0b3351c95c..ac2ac2f6884 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/security/ApplicationContextRequestMatcher.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/security/servlet/ApplicationContextRequestMatcher.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.security;
+package org.springframework.boot.security.servlet;
import javax.servlet.http.HttpServletRequest;
@@ -53,7 +53,7 @@ public abstract class ApplicationContextRequestMatcher implements RequestMatc
}
@Override
- public boolean matches(HttpServletRequest request) {
+ public final boolean matches(HttpServletRequest request) {
return matches(request, getContext(request));
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/security/reactive/ApplicationContextServerWebExchangeMatcherTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/security/reactive/ApplicationContextServerWebExchangeMatcherTests.java
new file mode 100644
index 00000000000..7b25acdc66d
--- /dev/null
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/security/reactive/ApplicationContextServerWebExchangeMatcherTests.java
@@ -0,0 +1,157 @@
+/*
+ * 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.reactive;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import reactor.core.publisher.Mono;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
+import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebHandler;
+import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link ApplicationContextServerWebExchangeMatcher}.
+ *
+ * @author Madhura Bhave
+ */
+public class ApplicationContextServerWebExchangeMatcherTests {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void createWhenContextClassIsNullShouldThrowException() {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Context class must not be null");
+ new TestApplicationContextServerWebExchangeMatcher<>(null);
+ }
+
+ @Test
+ public void matchesWhenContextClassIsApplicationContextShouldProvideContext() {
+ ServerWebExchange exchange = createHttpWebHandlerAdapter();
+ StaticApplicationContext context = (StaticApplicationContext) exchange.getApplicationContext();
+ assertThat(new TestApplicationContextServerWebExchangeMatcher<>(ApplicationContext.class)
+ .callMatchesAndReturnProvidedContext(exchange)).isEqualTo(context);
+ }
+
+ @Test
+ public void matchesWhenContextClassIsExistingBeanShouldProvideBean() {
+ ServerWebExchange exchange = createHttpWebHandlerAdapter();
+ StaticApplicationContext context = (StaticApplicationContext) exchange.getApplicationContext();
+ context.registerSingleton("existingBean", ExistingBean.class);
+ assertThat(new TestApplicationContextServerWebExchangeMatcher<>(ExistingBean.class)
+ .callMatchesAndReturnProvidedContext(exchange))
+ .isEqualTo(context.getBean(ExistingBean.class));
+ }
+
+ @Test
+ public void matchesWhenContextClassIsNewBeanShouldProvideBean() {
+ ServerWebExchange exchange = createHttpWebHandlerAdapter();
+ StaticApplicationContext context = (StaticApplicationContext) exchange.getApplicationContext();
+ context.registerSingleton("existingBean", ExistingBean.class);
+ assertThat(new TestApplicationContextServerWebExchangeMatcher<>(NewBean.class)
+ .callMatchesAndReturnProvidedContext(exchange).getBean())
+ .isEqualTo(context.getBean(ExistingBean.class));
+ }
+
+ @Test
+ public void matchesWhenContextIsNull() {
+ MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/path").build());
+ this.thrown.expect(IllegalStateException.class);
+ this.thrown.expectMessage("No WebApplicationContext found.");
+ new TestApplicationContextServerWebExchangeMatcher<>(ExistingBean.class)
+ .callMatchesAndReturnProvidedContext(exchange);
+ }
+
+ private ServerWebExchange createHttpWebHandlerAdapter() {
+ StaticApplicationContext context = new StaticApplicationContext();
+ TestHttpWebHandlerAdapter adapter = new TestHttpWebHandlerAdapter(mock(WebHandler.class));
+ adapter.setApplicationContext(context);
+ return adapter.createExchange(MockServerHttpRequest.get("/path").build(), new MockServerHttpResponse());
+ }
+
+ static class TestHttpWebHandlerAdapter extends HttpWebHandlerAdapter {
+
+ TestHttpWebHandlerAdapter(WebHandler delegate) {
+ super(delegate);
+ }
+
+ @Override
+ protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttpResponse response) {
+ return super.createExchange(request, response);
+ }
+
+ }
+
+ static class ExistingBean {
+
+ }
+
+ static class NewBean {
+
+ private final ExistingBean bean;
+
+ NewBean(ExistingBean bean) {
+ this.bean = bean;
+ }
+
+ public ExistingBean getBean() {
+ return this.bean;
+ }
+
+ }
+
+ static class TestApplicationContextServerWebExchangeMatcher
+ extends ApplicationContextServerWebExchangeMatcher {
+
+ private C providedContext;
+
+ TestApplicationContextServerWebExchangeMatcher(Class extends C> context) {
+ super(context);
+ }
+
+ C callMatchesAndReturnProvidedContext(ServerWebExchange exchange) {
+ matches(exchange);
+ return getProvidedContext();
+ }
+
+ @Override
+ protected Mono matches(ServerWebExchange exchange, C context) {
+ this.providedContext = context;
+ return MatchResult.match();
+ }
+
+ C getProvidedContext() {
+ return this.providedContext;
+ }
+
+ }
+
+}
+
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/security/ApplicationContextRequestMatcherTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/security/servlet/ApplicationContextRequestMatcherTests.java
similarity index 98%
rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/security/ApplicationContextRequestMatcherTests.java
rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/security/servlet/ApplicationContextRequestMatcherTests.java
index b6cb1e3fdf8..eb656ead81e 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/security/ApplicationContextRequestMatcherTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/security/servlet/ApplicationContextRequestMatcherTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.security;
+package org.springframework.boot.security.servlet;
import javax.servlet.http.HttpServletRequest;
diff --git a/spring-boot-samples/spring-boot-sample-actuator-custom-security/src/main/java/sample/actuator/customsecurity/SecurityConfiguration.java b/spring-boot-samples/spring-boot-sample-actuator-custom-security/src/main/java/sample/actuator/customsecurity/SecurityConfiguration.java
index 09e84151d8c..945d6ebb819 100644
--- a/spring-boot-samples/spring-boot-sample-actuator-custom-security/src/main/java/sample/actuator/customsecurity/SecurityConfiguration.java
+++ b/spring-boot-samples/spring-boot-sample-actuator-custom-security/src/main/java/sample/actuator/customsecurity/SecurityConfiguration.java
@@ -16,7 +16,7 @@
package sample.actuator.customsecurity;
-import org.springframework.boot.actuate.autoconfigure.security.EndpointRequest;
+import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.autoconfigure.security.StaticResourceRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
diff --git a/spring-boot-samples/spring-boot-sample-secure-webflux/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-secure-webflux/src/main/resources/application.properties
index cb7e159bf3a..093ae1eb74b 100644
--- a/spring-boot-samples/spring-boot-sample-secure-webflux/src/main/resources/application.properties
+++ b/spring-boot-samples/spring-boot-sample-secure-webflux/src/main/resources/application.properties
@@ -1,2 +1,3 @@
spring.security.user.name=user
-spring.security.user.password=password
\ No newline at end of file
+spring.security.user.password=password
+management.endpoints.web.expose=*
\ No newline at end of file
diff --git a/spring-boot-samples/spring-boot-sample-secure-webflux/src/test/java/sample/secure/webflux/SampleSecureWebFluxCustomSecurityTests.java b/spring-boot-samples/spring-boot-sample-secure-webflux/src/test/java/sample/secure/webflux/SampleSecureWebFluxCustomSecurityTests.java
new file mode 100644
index 00000000000..db6e899b491
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-secure-webflux/src/test/java/sample/secure/webflux/SampleSecureWebFluxCustomSecurityTests.java
@@ -0,0 +1,115 @@
+/*
+ * 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 sample.secure.webflux;
+
+import java.util.Base64;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.autoconfigure.security.reactive.EndpointRequest;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+/**
+ * Integration tests for a secure reactive application with custom security.
+ *
+ * @author Madhura Bhave
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { SampleSecureWebFluxCustomSecurityTests.SecurityConfiguration.class,
+ SampleSecureWebFluxApplication.class })
+public class SampleSecureWebFluxCustomSecurityTests {
+
+ @Autowired
+ private WebTestClient webClient;
+
+ @Test
+ public void userDefinedMappingsSecure() {
+ this.webClient.get().uri("/").accept(MediaType.APPLICATION_JSON).exchange()
+ .expectStatus().isEqualTo(HttpStatus.UNAUTHORIZED);
+ }
+
+ @Test
+ public void healthAndInfoDontRequireAuthentication() {
+ this.webClient.get().uri("/actuator/health").accept(MediaType.APPLICATION_JSON)
+ .exchange().expectStatus().isOk();
+ this.webClient.get().uri("/actuator/info").accept(MediaType.APPLICATION_JSON)
+ .exchange().expectStatus().isOk();
+ }
+
+ @Test
+ public void actuatorsSecuredByRole() {
+ this.webClient.get().uri("/actuator/env").accept(MediaType.APPLICATION_JSON)
+ .header("Authorization", "basic " + getBasicAuth()).exchange()
+ .expectStatus().isForbidden();
+ }
+
+ @Test
+ public void actuatorsAccessibleOnCorrectLogin() {
+ this.webClient.get().uri("/actuator/env").accept(MediaType.APPLICATION_JSON)
+ .header("Authorization", "basic " + getBasicAuthForAdmin()).exchange()
+ .expectStatus().isOk();
+ }
+
+ @Configuration
+ static class SecurityConfiguration {
+
+ @Bean
+ public MapReactiveUserDetailsService userDetailsService() {
+ return new MapReactiveUserDetailsService(
+ User.withDefaultPasswordEncoder().username("user").password("password")
+ .authorities("ROLE_USER").build(),
+ User.withDefaultPasswordEncoder().username("admin").password("admin")
+ .authorities("ROLE_ACTUATOR", "ROLE_USER").build());
+ }
+
+ @Bean
+ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+ http
+ .authorizeExchange()
+ .matchers(EndpointRequest.to("health", "info")).permitAll()
+ .matchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR")
+ .pathMatchers("/login").permitAll()
+ .anyExchange().authenticated()
+ .and()
+ .httpBasic();
+ return http.build();
+ }
+
+ }
+
+ private String getBasicAuth() {
+ return new String(Base64.getEncoder().encode(("user:password").getBytes()));
+ }
+
+ private String getBasicAuthForAdmin() {
+ return new String(Base64.getEncoder().encode(("admin:admin").getBytes()));
+ }
+
+}
+
diff --git a/spring-boot-samples/spring-boot-sample-web-method-security/src/main/java/sample/security/method/SampleMethodSecurityApplication.java b/spring-boot-samples/spring-boot-sample-web-method-security/src/main/java/sample/security/method/SampleMethodSecurityApplication.java
index 101c60be4e8..6789e3fcb11 100644
--- a/spring-boot-samples/spring-boot-sample-web-method-security/src/main/java/sample/security/method/SampleMethodSecurityApplication.java
+++ b/spring-boot-samples/spring-boot-sample-web-method-security/src/main/java/sample/security/method/SampleMethodSecurityApplication.java
@@ -19,7 +19,7 @@ package sample.security.method;
import java.util.Date;
import java.util.Map;
-import org.springframework.boot.actuate.autoconfigure.security.EndpointRequest;
+import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;