From 544f39f8261dd91d9e08ce4d8f56038d85deb105 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Sat, 15 Jul 2017 21:55:23 -0500 Subject: [PATCH] Add Annotated Support for WebTestClient Fixes gh-4457 --- .../server/SecurityMockServerConfigurers.java | 13 +- .../AbstractMockServerConfigurersTests.java | 80 ++++++++++++ ...tyMockServerConfigurersAnnotatedTests.java | 118 ++++++++++++++++++ ...kServerConfigurersClassAnnotatedTests.java | 90 +++++++++++++ .../SecurityMockServerConfigurersTests.java | 47 +------ 5 files changed, 300 insertions(+), 48 deletions(-) create mode 100644 test/src/test/java/org/springframework/security/test/web/reactive/server/AbstractMockServerConfigurersTests.java create mode 100644 test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersAnnotatedTests.java create mode 100644 test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersClassAnnotatedTests.java diff --git a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java index 54a66bf483..ae230d61ac 100644 --- a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java +++ b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java @@ -25,6 +25,7 @@ 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.security.test.context.TestSecurityContextHolder; import org.springframework.test.web.reactive.server.MockServerConfigurer; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClientConfigurer; @@ -39,6 +40,7 @@ import java.util.Collection; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; /** * Test utilities for working with Spring Security and @@ -56,7 +58,10 @@ public class SecurityMockServerConfigurers { public static MockServerConfigurer springSecurity() { return new MockServerConfigurer() { public void beforeServerCreated(WebHttpHandlerBuilder builder) { - builder.filters( filters -> filters.add(0, new MutatorFilter())); + builder.filters( filters -> { + filters.add(0, new MutatorFilter()); + filters.add(0, new SetupMutatorFilter(createMutator( () -> TestSecurityContextHolder.getContext().getAuthentication()))); + }); } }; } @@ -68,7 +73,7 @@ public class SecurityMockServerConfigurers { * @return the {@link WebTestClientConfigurer} to use */ public static T mockPrincipal(Principal principal) { - return (T) new MutatorWebTestClientConfigurer(m -> m.mutate().principal(Mono.just(principal)).build()); + return (T) new MutatorWebTestClientConfigurer(createMutator(() -> principal)); } /** @@ -115,6 +120,10 @@ public class SecurityMockServerConfigurers { return new UserExchangeMutator(username); } + private static Function createMutator(Supplier principal) { + return m -> principal.get() == null ? m : m.mutate().principal(Mono.just(principal.get())).build(); + } + /** * Updates the WebServerExchange using {@code {@link SecurityMockServerConfigurers#mockUser(UserDetails)}. Defaults to use a * password of "password" and granted authorities of "ROLE_USER". diff --git a/test/src/test/java/org/springframework/security/test/web/reactive/server/AbstractMockServerConfigurersTests.java b/test/src/test/java/org/springframework/security/test/web/reactive/server/AbstractMockServerConfigurersTests.java new file mode 100644 index 0000000000..8c4d1eca31 --- /dev/null +++ b/test/src/test/java/org/springframework/security/test/web/reactive/server/AbstractMockServerConfigurersTests.java @@ -0,0 +1,80 @@ +/* + * + * * 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.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +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.springSecurity; + +/** + * @author Rob Winch + * @since 5.0 + */ +abstract class AbstractMockServerConfigurersTests { + protected PrincipalController controller = new PrincipalController(); + + protected User.UserBuilder userBuilder = User + .withUsername("user") + .password("password") + .roles("USER"); + + protected 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 + protected 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; + } + } +} diff --git a/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersAnnotatedTests.java b/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersAnnotatedTests.java new file mode 100644 index 0000000000..ac56ace524 --- /dev/null +++ b/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersAnnotatedTests.java @@ -0,0 +1,118 @@ +/* + * + * * 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.junit.runner.RunWith; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.security.test.context.TestSecurityContextHolder; +import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +import java.security.Principal; + +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockPrincipal; +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; + +/** + * @author Rob Winch + * @since 5.0 + */ +@RunWith(SpringRunner.class) +@SecurityTestExecutionListeners +public class SecurityMockServerConfigurersAnnotatedTests extends AbstractMockServerConfigurersTests { + + WebTestClient client = WebTestClient + .bindToController(controller) + .apply(springSecurity()) + .configureClient() + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); + + @Test + @WithMockUser + public void withMockUserWhenOnMethodThenSuccess() { + client + .get() + .exchange() + .expectStatus().isOk(); + + Authentication authentication = TestSecurityContextHolder.getContext().getAuthentication(); + controller.assertPrincipalIsEqualTo(authentication); + } + + @Test + @WithMockUser + public void withMockUserWhenGlobalMockPrincipalThenOverridesAnnotation() { + Principal principal = () -> "principal"; + client = WebTestClient + .bindToController(controller) + .apply(springSecurity()) + .apply(mockPrincipal(principal)) + .configureClient() + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); + + client + .get() + .exchange() + .expectStatus().isOk(); + + controller.assertPrincipalIsEqualTo(principal); + } + + @Test + @WithMockUser + public void withMockUserWhenMutateWithMockPrincipalThenOverridesAnnotation() { + Principal principal = () -> "principal"; + client + .mutateWith(mockPrincipal(principal)) + .get() + .exchange() + .expectStatus().isOk(); + + controller.assertPrincipalIsEqualTo(principal); + } + + @Test + @WithMockUser + public void withMockUserWhenMutateWithMockPrincipalAndNoMutateThenOverridesAnnotationAndUsesAnnotation() { + Principal principal = () -> "principal"; + client + .mutateWith(mockPrincipal(principal)) + .get() + .exchange() + .expectStatus().isOk(); + + controller.assertPrincipalIsEqualTo(principal); + + + client + .get() + .exchange() + .expectStatus().isOk(); + + principal = controller.removePrincipal(); + assertPrincipalCreatedFromUserDetails(principal, userBuilder.build()); + } +} diff --git a/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersClassAnnotatedTests.java b/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersClassAnnotatedTests.java new file mode 100644 index 0000000000..9ccf699284 --- /dev/null +++ b/test/src/test/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurersClassAnnotatedTests.java @@ -0,0 +1,90 @@ +/* + * + * * 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.junit.runner.RunWith; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.security.test.context.TestSecurityContextHolder; +import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +import java.security.Principal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser; +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; + +/** + * @author Rob Winch + * @since 5.0 + */ +@WithMockUser +@RunWith(SpringRunner.class) +@SecurityTestExecutionListeners +public class SecurityMockServerConfigurersClassAnnotatedTests extends AbstractMockServerConfigurersTests { + WebTestClient client = WebTestClient + .bindToController(controller) + .apply(springSecurity()) + .configureClient() + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); + + @Test + public void wheMockUserWhenClassAnnotatedThenSuccess() { + client + .get() + .exchange() + .expectStatus().isOk() + .expectBody(String.class).consumeWith( response -> assertThat(response.getResponseBody()).contains("\"username\":\"user\"")); + + Authentication authentication = TestSecurityContextHolder.getContext().getAuthentication(); + controller.assertPrincipalIsEqualTo(authentication); + } + + @Test + @WithMockUser("method-user") + public void withMockUserWhenClassAndMethodAnnotationThenMethodOverrides() { + client + .get() + .exchange() + .expectStatus().isOk() + .expectBody(String.class).consumeWith( response -> assertThat(response.getResponseBody()).contains("\"username\":\"method-user\"")); + + Authentication authentication = TestSecurityContextHolder.getContext().getAuthentication(); + controller.assertPrincipalIsEqualTo(authentication); + } + + @Test + public void withMockUserWhenMutateWithThenMustateWithOverrides() { + client + .mutateWith(mockUser("mutateWith-mockUser")) + .get() + .exchange() + .expectStatus().isOk() + .expectBody(String.class).consumeWith( response -> assertThat(response.getResponseBody()).contains("\"username\":\"mutateWith-mockUser\"")); + + Principal principal = controller.removePrincipal(); + assertPrincipalCreatedFromUserDetails(principal, userBuilder.username("mutateWith-mockUser").build()); + } +} 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 index 9d1e91f19c..9df1f261ba 100644 --- 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 @@ -22,25 +22,19 @@ 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(); - +public class SecurityMockServerConfigurersTests extends AbstractMockServerConfigurersTests { WebTestClient client = WebTestClient .bindToController(controller) .apply(springSecurity()) @@ -48,11 +42,6 @@ public class SecurityMockServerConfigurersTests { .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"; @@ -197,38 +186,4 @@ public class SecurityMockServerConfigurersTests { 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; - } - } }