From 03f2d654ad83ae8526380f06891bd09c91099fae Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 13 Jul 2017 20:47:09 -0500 Subject: [PATCH] Fix WebTestClient Support Fixes gh-4419 --- .../sample/HelloWebfluxApplicationTests.java | 46 ++-- .../HelloWebfluxFnApplicationITests.java | 2 - .../HelloWebfluxFnApplicationTests.java | 45 ++-- test/spring-security-test.gradle | 3 + ...ava => SecurityMockServerConfigurers.java} | 130 +++++++-- .../server/SecurityExchangeMutatorsTests.java | 108 -------- .../SecurityMockServerConfigurersTests.java | 254 ++++++++++++++++++ 7 files changed, 414 insertions(+), 174 deletions(-) rename test/src/main/java/org/springframework/security/test/web/reactive/server/{SecurityExchangeMutators.java => SecurityMockServerConfigurers.java} (50%) delete mode 100644 test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityExchangeMutatorsTests.java create mode 100644 test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersTests.java diff --git a/samples/javaconfig/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.java b/samples/javaconfig/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.java index 8bbd159bc6..7a6e61c6af 100644 --- a/samples/javaconfig/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.java +++ b/samples/javaconfig/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.java @@ -25,6 +25,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; +import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers; import org.springframework.security.web.server.header.ContentTypeOptionsHttpHeadersWriter; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; @@ -36,7 +37,7 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import java.nio.charset.Charset; import java.util.Base64; -import static org.springframework.security.test.web.reactive.server.SecurityExchangeMutators.withUser; +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; /** @@ -172,26 +173,29 @@ public class HelloWebfluxApplicationTests { .expectStatus().isOk(); } -// @Test -// public void mockSupport() throws Exception { -// ExchangeMutatorWebFilter exchangeMutator = new ExchangeMutatorWebFilter(); -// WebTestClient mockRest = WebTestClient.bindToApplicationContext(this.context).webFilter(exchangeMutator).build(); -// -// mockRest -// .mutate() -// .filter(exchangeMutator.perClient(withUser())) -// .build() -// .get() -// .uri("/principal") -// .exchange() -// .expectStatus().isOk(); -// -// mockRest -// .get() -// .uri("/principal") -// .exchange() -// .expectStatus().isUnauthorized(); -// } + @Test + public void mockSupport() throws Exception { + WebTestClient mockRest = WebTestClient + .bindToApplicationContext(this.context) + .apply(springSecurity()) + .build(); + + mockRest + .mutate() + .apply(SecurityMockServerConfigurers.mockUser()) + .build() + .get() + .uri("/principal") + .exchange() + .expectStatus().isOk() + .expectBody(String.class).isEqualTo("{\"username\":\"user\"}"); + + mockRest + .get() + .uri("/principal") + .exchange() + .expectStatus().isUnauthorized(); + } @Test public void me() throws Exception { diff --git a/samples/javaconfig/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java b/samples/javaconfig/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java index 60a4debb3c..7d44430c46 100644 --- a/samples/javaconfig/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java +++ b/samples/javaconfig/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java @@ -21,7 +21,6 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseCookie; import org.springframework.security.web.server.header.ContentTypeOptionsHttpHeadersWriter; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; @@ -34,7 +33,6 @@ import java.nio.charset.Charset; import java.time.Duration; import java.util.Base64; -import static org.springframework.security.test.web.reactive.server.SecurityExchangeMutators.withUser; import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; /** diff --git a/samples/javaconfig/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java b/samples/javaconfig/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java index 8d8c7fd957..e09bcb08df 100644 --- a/samples/javaconfig/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java +++ b/samples/javaconfig/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; +import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers; import org.springframework.security.web.server.WebFilterChainFilter; import org.springframework.security.web.server.header.ContentTypeOptionsHttpHeadersWriter; import org.springframework.test.context.ActiveProfiles; @@ -37,7 +38,7 @@ import org.springframework.web.reactive.function.server.RouterFunction; import java.nio.charset.Charset; import java.util.Base64; -import static org.springframework.security.test.web.reactive.server.SecurityExchangeMutators.withUser; +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; /** @@ -178,26 +179,28 @@ public class HelloWebfluxFnApplicationTests { .expectStatus().isOk(); } -// @Test -// public void mockSupport() throws Exception { -// ExchangeMutatorWebFilter exchangeMutator = new ExchangeMutatorWebFilter(); -// WebTestClient mockRest = WebTestClient.bindToRouterFunction(this.routerFunction).webFilter(exchangeMutator, springSecurityFilterChain).build(); -// -// mockRest -// .mutate() -// .filter(exchangeMutator.perClient(withUser())) -// .build() -// .get() -// .uri("/principal") -// .exchange() -// .expectStatus().isOk(); -// -// mockRest -// .get() -// .uri("/principal") -// .exchange() -// .expectStatus().isUnauthorized(); -// } + @Test + public void mockSupport() throws Exception { + WebTestClient mockRest = WebTestClient.bindToRouterFunction(this.routerFunction) + .webFilter(springSecurityFilterChain) + .apply(springSecurity()) + .build(); + + mockRest + .mutate() + .apply(SecurityMockServerConfigurers.mockUser()) + .build() + .get() + .uri("/principal") + .exchange() + .expectStatus().isOk(); + + mockRest + .get() + .uri("/principal") + .exchange() + .expectStatus().isUnauthorized(); + } @Test public void principal() throws Exception { diff --git a/test/spring-security-test.gradle b/test/spring-security-test.gradle index fd982b2c6f..02fac497a9 100644 --- a/test/spring-security-test.gradle +++ b/test/spring-security-test.gradle @@ -8,9 +8,12 @@ dependencies { optional project(':spring-security-config') optional 'io.projectreactor:reactor-core' + optional 'org.springframework:spring-webflux' provided 'javax.servlet:javax.servlet-api' + testCompile 'com.fasterxml.jackson.core:jackson-databind' + testCompile 'org.skyscreamer:jsonassert' testCompile 'org.springframework:spring-webmvc' testCompile 'org.springframework:spring-tx' testCompile powerMockDependencies diff --git a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityExchangeMutators.java b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java similarity index 50% rename from test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityExchangeMutators.java rename to test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java index 3023ce0d97..54a66bf483 100644 --- a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityExchangeMutators.java +++ b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java @@ -18,46 +18,67 @@ package org.springframework.security.test.web.reactive.server; +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.lang.Nullable; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.test.web.reactive.server.MockServerConfigurer; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClientConfigurer; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import reactor.core.publisher.Mono; import java.security.Principal; import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.UnaryOperator; /** * Test utilities for working with Spring Security and - * {{@link org.springframework.test.web.reactive.server.WebTestClient}} using - * {{{@link org.springframework.test.web.reactive.server.ExchangeMutatorWebFilter}}}. + * {{@link org.springframework.test.web.reactive.server.WebTestClient.Builder#apply(WebTestClientConfigurer)}}. * * @author Rob Winch * @since 5.0 */ -public class SecurityExchangeMutators { +public class SecurityMockServerConfigurers { + + /** + * Sets up Spring Security's {@link WebTestClient} test support + * @return the MockServerConfigurer to use + */ + public static MockServerConfigurer springSecurity() { + return new MockServerConfigurer() { + public void beforeServerCreated(WebHttpHandlerBuilder builder) { + builder.filters( filters -> filters.add(0, new MutatorFilter())); + } + }; + } + /** * Updates the ServerWebExchange to use the provided Principal * * @param principal the principal to use. - * @return the {@link Function}} to use + * @return the {@link WebTestClientConfigurer} to use */ - public static Function withPrincipal(Principal principal) { - return m -> m.mutate().principal(Mono.just(principal)).build(); + public static T mockPrincipal(Principal principal) { + return (T) new MutatorWebTestClientConfigurer(m -> m.mutate().principal(Mono.just(principal)).build()); } /** * Updates the ServerWebExchange to use the provided Authentication as the Principal * * @param authentication the Authentication to use. - * @return the {@link Function}} to use + * @return the {@link WebTestClientConfigurer}} to use */ - public static Function withAuthentication(Authentication authentication) { - return withPrincipal(authentication); + public static T mockAuthentication(Authentication authentication) { + return mockPrincipal(authentication); } /** @@ -65,10 +86,10 @@ public class SecurityExchangeMutators { * the Principal * * @param userDetails the UserDetails to use. - * @return the {@link Function}} to use + * @return the {@link WebTestClientConfigurer} to use */ - public static Function withUser(UserDetails userDetails) { - return withAuthentication(new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities())); + public static T mockUser(UserDetails userDetails) { + return mockAuthentication(new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities())); } /** @@ -76,10 +97,10 @@ public class SecurityExchangeMutators { * the Principal. This uses a default username of "user", password of "password", and granted authorities of * "ROLE_USER". * - * @return the {@link Function}} to use + * @return the {@link UserExchangeMutator} to use */ - public static UserExchangeMutator withUser() { - return withUser("user"); + public static UserExchangeMutator mockUser() { + return mockUser("user"); } @@ -88,17 +109,17 @@ public class SecurityExchangeMutators { * the Principal. This uses a default password of "password" and granted authorities of * "ROLE_USER". * - * @return the {@link Function}} to use + * @return the {@link WebTestClientConfigurer} to use */ - public static UserExchangeMutator withUser(String username) { + public static UserExchangeMutator mockUser(String username) { return new UserExchangeMutator(username); } /** - * Updates the WebServerExchange using {@code SecurityExchangeMutators#withUser(UserDetails)}. Defaults to use a + * Updates the WebServerExchange using {@code {@link SecurityMockServerConfigurers#mockUser(UserDetails)}. Defaults to use a * password of "password" and granted authorities of "ROLE_USER". */ - public static class UserExchangeMutator implements Function { + public static class UserExchangeMutator implements WebTestClientConfigurer, MockServerConfigurer { private final User.UserBuilder userBuilder; private UserExchangeMutator(String username) { @@ -182,8 +203,73 @@ public class SecurityExchangeMutators { } @Override - public ServerWebExchange apply(ServerWebExchange serverWebExchange) { - return withUser(userBuilder.build()).apply(serverWebExchange); + public void beforeServerCreated(WebHttpHandlerBuilder builder) { + configurer().beforeServerCreated(builder); + } + + @Override + public void afterConfigureAdded(WebTestClient.MockServerSpec serverSpec) { + configurer().afterConfigureAdded(serverSpec); + } + + @Override + public void afterConfigurerAdded(WebTestClient.Builder builder, @Nullable WebHttpHandlerBuilder webHttpHandlerBuilder, @Nullable ClientHttpConnector clientHttpConnector) { + configurer().afterConfigurerAdded(builder, webHttpHandlerBuilder, clientHttpConnector); + } + + private T configurer() { + return mockUser(userBuilder.build()); + } + } + + private static class MutatorWebTestClientConfigurer implements WebTestClientConfigurer, MockServerConfigurer { + private final Function mutator; + + private MutatorWebTestClientConfigurer(Function mutator) { + this.mutator = mutator; + } + + @Override + public void beforeServerCreated(WebHttpHandlerBuilder builder) { + builder.filters(addSetupMutatorFilter()); + } + + @Override + public void afterConfigurerAdded(WebTestClient.Builder builder, @Nullable WebHttpHandlerBuilder webHttpHandlerBuilder, @Nullable ClientHttpConnector clientHttpConnector) { + webHttpHandlerBuilder.filters(addSetupMutatorFilter()); + } + + private Consumer> addSetupMutatorFilter() { + return filters -> filters.add(0, new SetupMutatorFilter(mutator)); + } + } + + private static class SetupMutatorFilter implements WebFilter { + private final Function mutator; + + private SetupMutatorFilter(Function mutator) { + this.mutator = mutator; + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain webFilterChain) { + exchange.getAttributes().computeIfAbsent(MutatorFilter.ATTRIBUTE_NAME, key -> mutator); + return webFilterChain.filter(exchange); + } + } + + private static class MutatorFilter implements WebFilter { + + public static final String ATTRIBUTE_NAME = "mutator"; + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain webFilterChain) { + Function mutator = exchange.getAttribute(ATTRIBUTE_NAME); + if(mutator != null) { + exchange.getAttributes().remove(ATTRIBUTE_NAME); + exchange = mutator.apply(exchange); + } + return webFilterChain.filter(exchange); } } } diff --git a/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityExchangeMutatorsTests.java b/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityExchangeMutatorsTests.java deleted file mode 100644 index 1f789950c1..0000000000 --- a/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityExchangeMutatorsTests.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * - * * Copyright 2002-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.security.test.web.reactive.server; - -import org.assertj.core.api.AssertionsForInterfaceTypes; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.web.server.ServerWebExchange; - -import java.security.Principal; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.reactive.server.SecurityExchangeMutators.withAuthentication; -import static org.springframework.security.test.web.reactive.server.SecurityExchangeMutators.withPrincipal; -import static org.springframework.security.test.web.reactive.server.SecurityExchangeMutators.withUser; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RunWith(MockitoJUnitRunner.class) -public class SecurityExchangeMutatorsTests { - @Mock - Principal principal; - @Mock - Authentication authentication; - - ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange(); - - User.UserBuilder userBuilder = User.withUsername("user").password("password").roles("USER"); - - @Test - public void withPrincipalWhenHappyPathThenSuccess() { - assertThat(withPrincipal(principal).apply(exchange).getPrincipal().block()).isEqualTo(principal); - } - - @Test - public void withAuthenticationWhenHappyPathThenSuccess() { - assertThat(withAuthentication(authentication).apply(exchange).getPrincipal().block()).isEqualTo(authentication); - } - - @Test - public void withUserWhenDefaultsThenSuccess() { - Principal principal = withUser().apply(exchange).getPrincipal().block(); - - assertPrincipalCreatedFromUserDetails(principal, userBuilder.build()); - } - - @Test - public void withUserStringWhenHappyPathThenSuccess() { - Principal principal = withUser(userBuilder.build().getUsername() ).apply(exchange).getPrincipal().block(); - - assertPrincipalCreatedFromUserDetails(principal, userBuilder.build()); - } - - @Test - public void withUserStringWhenCustomThenSuccess() { - SecurityExchangeMutators.UserExchangeMutator withUser = withUser("admin").password("secret").roles("USER", "ADMIN"); - userBuilder = User.withUsername("admin").password("secret").roles("USER", "ADMIN"); - - Principal principal = withUser.apply(exchange).getPrincipal().block(); - - assertPrincipalCreatedFromUserDetails(principal, userBuilder.build() ); - } - - @Test - public void withUserUserDetailsWhenHappyPathThenSuccess() { - Principal principal = withUser(userBuilder.build()).apply(exchange).getPrincipal().block(); - - assertPrincipalCreatedFromUserDetails(principal, userBuilder.build()); - } - - private void assertPrincipalCreatedFromUserDetails(Principal principal, UserDetails originalUserDetails) { - assertThat(principal).isInstanceOf(UsernamePasswordAuthenticationToken.class); - - UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) principal; - assertThat(authentication.getCredentials()).isEqualTo(originalUserDetails.getPassword()); - assertThat(authentication.getAuthorities()).containsOnlyElementsOf(originalUserDetails.getAuthorities()); - - UserDetails userDetails = (UserDetails) authentication.getPrincipal(); - assertThat(userDetails.getPassword()).isEqualTo(authentication.getCredentials()); - assertThat(authentication.getAuthorities()).containsOnlyElementsOf(userDetails.getAuthorities()); - } -} diff --git a/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersTests.java b/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersTests.java new file mode 100644 index 0000000000..f64efc7634 --- /dev/null +++ b/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersTests.java @@ -0,0 +1,254 @@ +/* + * + * * Copyright 2002-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.security.test.web.reactive.server; + +import org.junit.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.*; + +/** + * @author Rob Winch + * @since 5.0 + */ +public class SecurityMockServerConfigurersTests { + PrincipalController controller = new PrincipalController(); + + WebTestClient client = WebTestClient + .bindToController(controller) + .apply(springSecurity()) + .configureClient() + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); + + User.UserBuilder userBuilder = User + .withUsername("user") + .password("password") + .roles("USER"); + + @Test + public void mockPrincipalWhenLocalThenSuccess() { + Principal principal = () -> "principal"; + client + .mutate() + .apply(mockPrincipal(principal)) + .build() + .get() + .exchange() + .expectStatus().isOk(); + + controller.assertPrincipalIsEqualTo(principal); + } + + @Test + public void mockPrincipalWhenGlobalTheWorks() { + Principal principal = () -> "principal"; + client = WebTestClient + .bindToController(controller) + .apply(springSecurity()) + .apply(mockPrincipal(principal)) + .configureClient() + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); + + client + .mutate() + .build() + .get() + .exchange() + .expectStatus().isOk(); + + controller.assertPrincipalIsEqualTo(principal); + } + + @Test + public void mockPrincipalWhenMultipleInvocationsThenLastInvocationWins() { + Principal principal = () -> "principal"; + client + .mutate() + .apply(mockPrincipal(() -> "will be overridden")) + .apply(mockPrincipal(principal)) + .build() + .get() + .exchange() + .expectStatus().isOk(); + + controller.assertPrincipalIsEqualTo(principal); + } + + @Test + public void mockAuthenticationWhenLocalThenSuccess() { + TestingAuthenticationToken authentication = new TestingAuthenticationToken("authentication", "secret", "ROLE_USER"); + client + .mutate() + .apply(mockAuthentication(authentication)) + .build() + .get() + .exchange() + .expectStatus().isOk(); + controller.assertPrincipalIsEqualTo(authentication); + } + + @Test + public void mockAuthenticationWhenGlobalThenSuccess() { + TestingAuthenticationToken authentication = new TestingAuthenticationToken("authentication", "secret", "ROLE_USER"); + client = WebTestClient + .bindToController(controller) + .apply(springSecurity()) + .apply(mockAuthentication(authentication)) + .configureClient() + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); + client + .mutate() + .build() + .get() + .exchange() + .expectStatus().isOk(); + controller.assertPrincipalIsEqualTo(authentication); + } + + @Test + public void mockUserWhenDefaultsThenSuccess() { + client + .mutate() + .apply(mockUser()) + .build() + .get() + .exchange() + .expectStatus().isOk(); + + Principal actual = controller.removePrincipal(); + + assertPrincipalCreatedFromUserDetails(actual, userBuilder.build()); + } + + @Test + public void mockUserWhenGlobalThenSuccess() { + client = WebTestClient + .bindToController(controller) + .apply(springSecurity()) + .apply(mockUser()) + .configureClient() + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); + client + .mutate() + .build() + .get() + .exchange() + .expectStatus().isOk(); + + Principal actual = controller.removePrincipal(); + + assertPrincipalCreatedFromUserDetails(actual, userBuilder.build()); + } + + @Test + public void mockUserStringWhenLocalThenSuccess() { + client + .mutate() + .apply(mockUser(userBuilder.build().getUsername())) + .build() + .get() + .exchange() + .expectStatus().isOk(); + + Principal actual = controller.removePrincipal(); + + assertPrincipalCreatedFromUserDetails(actual, userBuilder.build()); + } + + @Test + public void mockUserStringWhenCustomThenSuccess() { + this.userBuilder = User.withUsername("admin").password("secret").roles("USER", "ADMIN"); + client + .mutate() + .apply(mockUser("admin").password("secret").roles("USER", "ADMIN")) + .build() + .get() + .exchange() + .expectStatus().isOk(); + + Principal actual = controller.removePrincipal(); + + assertPrincipalCreatedFromUserDetails(actual, userBuilder.build()); + } + + @Test + public void mockUserUserDetailsLocalThenSuccess() { + UserDetails userDetails = this.userBuilder.build(); + client + .mutate() + .apply(mockUser(userDetails)) + .build() + .get() + .exchange() + .expectStatus().isOk(); + + Principal actual = controller.removePrincipal(); + + assertPrincipalCreatedFromUserDetails(actual, userBuilder.build()); + } + + private void assertPrincipalCreatedFromUserDetails(Principal principal, UserDetails originalUserDetails) { + assertThat(principal).isInstanceOf(UsernamePasswordAuthenticationToken.class); + + UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) principal; + assertThat(authentication.getCredentials()).isEqualTo(originalUserDetails.getPassword()); + assertThat(authentication.getAuthorities()).containsOnlyElementsOf(originalUserDetails.getAuthorities()); + + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + assertThat(userDetails.getPassword()).isEqualTo(authentication.getCredentials()); + assertThat(authentication.getAuthorities()).containsOnlyElementsOf(userDetails.getAuthorities()); + } + + @RestController + static class PrincipalController { + Principal principal; + + @RequestMapping("/**") + public Principal get(Principal principal) { + this.principal = principal; + return principal; + } + + public Principal removePrincipal() { + Principal result = this.principal; + this.principal = null; + return result; + } + + public void assertPrincipalIsEqualTo(Principal expected) { + assertThat(this.principal).isEqualTo(expected); + this.principal = null; + } + } +}