2
0
Эх сурвалжийг харах

Fix WebTestClient Support

Fixes gh-4419
Rob Winch 8 жил өмнө
parent
commit
03f2d654ad

+ 25 - 21
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 {

+ 0 - 2
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;
 
 /**

+ 24 - 21
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 {

+ 3 - 0
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

+ 108 - 22
test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityExchangeMutators.java → 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<ServerWebExchange, ServerWebExchange>}} to use
+	 * @return the {@link WebTestClientConfigurer} to use
 	 */
-	public static  Function<ServerWebExchange, ServerWebExchange> withPrincipal(Principal principal) {
-		return m -> m.mutate().principal(Mono.just(principal)).build();
+	public static <T extends WebTestClientConfigurer & MockServerConfigurer> 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<ServerWebExchange, ServerWebExchange>}} to use
+	 * @return the {@link WebTestClientConfigurer}} to use
 	 */
-	public static Function<ServerWebExchange, ServerWebExchange> withAuthentication(Authentication authentication) {
-		return withPrincipal(authentication);
+	public static <T extends WebTestClientConfigurer & MockServerConfigurer> 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<ServerWebExchange, ServerWebExchange>}} to use
+	 * @return the {@link WebTestClientConfigurer} to use
 	 */
-	public static  Function<ServerWebExchange, ServerWebExchange> withUser(UserDetails userDetails) {
-		return withAuthentication(new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()));
+	public static <T extends WebTestClientConfigurer & MockServerConfigurer> 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<ServerWebExchange, ServerWebExchange>}} 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<ServerWebExchange, ServerWebExchange>}} 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<ServerWebExchange, ServerWebExchange> {
+	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 extends WebTestClientConfigurer & MockServerConfigurer> T configurer() {
+			return mockUser(userBuilder.build());
+		}
+	}
+
+	private static class MutatorWebTestClientConfigurer implements WebTestClientConfigurer, MockServerConfigurer {
+		private final Function<ServerWebExchange, ServerWebExchange> mutator;
+
+		private MutatorWebTestClientConfigurer(Function<ServerWebExchange, ServerWebExchange> 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<List<WebFilter>> addSetupMutatorFilter() {
+			return filters -> filters.add(0, new SetupMutatorFilter(mutator));
+		}
+	}
+
+	private static class SetupMutatorFilter implements WebFilter {
+		private final Function<ServerWebExchange, ServerWebExchange> mutator;
+
+		private SetupMutatorFilter(Function<ServerWebExchange, ServerWebExchange> mutator) {
+			this.mutator = mutator;
+		}
+
+		@Override
+		public Mono<Void> 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<Void> filter(ServerWebExchange exchange, WebFilterChain webFilterChain) {
+			Function<ServerWebExchange, ServerWebExchange> mutator = exchange.getAttribute(ATTRIBUTE_NAME);
+			if(mutator != null) {
+				exchange.getAttributes().remove(ATTRIBUTE_NAME);
+				exchange = mutator.apply(exchange);
+			}
+			return webFilterChain.filter(exchange);
 		}
 	}
 }

+ 0 - 108
test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityExchangeMutatorsTests.java

@@ -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());
-	}
-}

+ 254 - 0
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;
+		}
+	}
+}