14 changed files with 785 additions and 30 deletions
@ -0,0 +1,182 @@
@@ -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: <pre class="code"> |
||||
* EndpointServerWebExchangeMatcher.toAnyEndpoint().excluding(ShutdownEndpoint.class) |
||||
* </pre> |
||||
* @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: <pre class="code"> |
||||
* EndpointRequest.to(ShutdownEndpoint.class, HealthEndpoint.class) |
||||
* </pre> |
||||
* @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: <pre class="code"> |
||||
* EndpointRequest.to("shutdown", "health") |
||||
* </pre> |
||||
* @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<EndpointPathProvider> { |
||||
|
||||
private final List<Object> includes; |
||||
|
||||
private final List<Object> 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<Object> includes, List<Object> excludes) { |
||||
super(EndpointPathProvider.class); |
||||
this.includes = includes; |
||||
this.excludes = excludes; |
||||
} |
||||
|
||||
EndpointServerWebExchangeMatcher excluding(Class<?>... endpoints) { |
||||
List<Object> excludes = new ArrayList<>(this.excludes); |
||||
excludes.addAll(Arrays.asList((Object[]) endpoints)); |
||||
return new EndpointServerWebExchangeMatcher(this.includes, excludes); |
||||
} |
||||
|
||||
EndpointServerWebExchangeMatcher excluding(String... endpoints) { |
||||
List<Object> excludes = new ArrayList<>(this.excludes); |
||||
excludes.addAll(Arrays.asList((Object[]) endpoints)); |
||||
return new EndpointServerWebExchangeMatcher(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 OrServerWebExchangeMatcher(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<ServerWebExchangeMatcher> getDelegateMatchers(Set<String> paths) { |
||||
return paths.stream().map((path) -> new PathPatternParserServerWebExchangeMatcher(path + "/**")) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
@Override |
||||
protected Mono<MatchResult> matches(ServerWebExchange exchange, |
||||
EndpointPathProvider context) { |
||||
return this.delegate.matches(exchange); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
15
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/EndpointRequest.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java
15
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/EndpointRequest.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java
@ -0,0 +1,190 @@
@@ -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<String> 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 { |
||||
|
||||
} |
||||
} |
||||
2
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/EndpointRequestTests.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java
2
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/EndpointRequestTests.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java
@ -0,0 +1,104 @@
@@ -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 <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 Madhura Bhave |
||||
* @since 2.0.0 |
||||
*/ |
||||
public abstract class ApplicationContextServerWebExchangeMatcher<C> 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<MatchResult> 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<MatchResult> 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); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,157 @@
@@ -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<C> |
||||
extends ApplicationContextServerWebExchangeMatcher<C> { |
||||
|
||||
private C providedContext; |
||||
|
||||
TestApplicationContextServerWebExchangeMatcher(Class<? extends C> context) { |
||||
super(context); |
||||
} |
||||
|
||||
C callMatchesAndReturnProvidedContext(ServerWebExchange exchange) { |
||||
matches(exchange); |
||||
return getProvidedContext(); |
||||
} |
||||
|
||||
@Override |
||||
protected Mono<MatchResult> matches(ServerWebExchange exchange, C context) { |
||||
this.providedContext = context; |
||||
return MatchResult.match(); |
||||
} |
||||
|
||||
C getProvidedContext() { |
||||
return this.providedContext; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
@ -1,2 +1,3 @@
@@ -1,2 +1,3 @@
|
||||
spring.security.user.name=user |
||||
spring.security.user.password=password |
||||
spring.security.user.password=password |
||||
management.endpoints.web.expose=* |
||||
@ -0,0 +1,115 @@
@@ -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())); |
||||
} |
||||
|
||||
} |
||||
|
||||
Loading…
Reference in new issue