diff --git a/test/src/main/java/org/springframework/security/test/context/support/ReactorContextTestExecutionListener.java b/test/src/main/java/org/springframework/security/test/context/support/ReactorContextTestExecutionListener.java index 1911dbfdf8..43b8af2ed1 100644 --- a/test/src/main/java/org/springframework/security/test/context/support/ReactorContextTestExecutionListener.java +++ b/test/src/main/java/org/springframework/security/test/context/support/ReactorContextTestExecutionListener.java @@ -19,6 +19,7 @@ package org.springframework.security.test.context.support; import org.reactivestreams.Subscription; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.test.context.TestSecurityContextHolder; import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; @@ -54,7 +55,8 @@ public class ReactorContextTestExecutionListener private static class DelegateTestExecutionListener extends AbstractTestExecutionListener { @Override public void beforeTestMethod(TestContext testContext) throws Exception { - Hooks.onLastOperator(Operators.lift((s, sub) -> new SecuritySubContext<>(sub))); + SecurityContext securityContext = TestSecurityContextHolder.getContext(); + Hooks.onLastOperator(Operators.lift((s, sub) -> new SecuritySubContext<>(sub, securityContext))); } @Override @@ -66,9 +68,11 @@ public class ReactorContextTestExecutionListener private static String CONTEXT_DEFAULTED_ATTR_NAME = SecuritySubContext.class.getName().concat(".CONTEXT_DEFAULTED_ATTR_NAME"); private final CoreSubscriber delegate; + private final SecurityContext securityContext; - SecuritySubContext(CoreSubscriber delegate) { + SecuritySubContext(CoreSubscriber delegate, SecurityContext securityContext) { this.delegate = delegate; + this.securityContext = securityContext; } @Override @@ -78,7 +82,7 @@ public class ReactorContextTestExecutionListener return context; } context = context.put(CONTEXT_DEFAULTED_ATTR_NAME, Boolean.TRUE); - Authentication authentication = TestSecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = securityContext.getAuthentication(); if (authentication == null) { return context; } diff --git a/test/src/test/java/org/springframework/security/test/context/support/ReactorContextTestExecutionListenerTests.java b/test/src/test/java/org/springframework/security/test/context/support/ReactorContextTestExecutionListenerTests.java index f66d642b4f..25851eb24d 100644 --- a/test/src/test/java/org/springframework/security/test/context/support/ReactorContextTestExecutionListenerTests.java +++ b/test/src/test/java/org/springframework/security/test/context/support/ReactorContextTestExecutionListenerTests.java @@ -21,6 +21,8 @@ package org.springframework.security.test.context.support; * @since 5.0 */ +import java.util.concurrent.ForkJoinPool; + import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -153,6 +155,18 @@ public class ReactorContextTestExecutionListenerTests { assertThat(comparator.compare(withSecurity, reactorContext)).isLessThan(0); } + @Test + public void checkSecurityContextResolutionWhenSubscribedContextCalledOnTheDifferentThreadThanWithSecurityContextTestExecutionListener() throws Exception { + TestingAuthenticationToken contextHolder = new TestingAuthenticationToken("contextHolder", "password", "ROLE_USER"); + TestSecurityContextHolder.setContext(new SecurityContextImpl(contextHolder)); + + this.listener.beforeTestMethod(this.testContext); + + ForkJoinPool.commonPool() + .submit(() -> assertAuthentication(contextHolder)) + .join(); + } + public void assertAuthentication(Authentication expected) { Mono authentication = ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication); 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 index 99b3eb051e..3c2d1fe703 100644 --- 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 @@ -52,7 +52,7 @@ abstract class AbstractMockServerConfigurersTests { @RestController protected static class PrincipalController { - Principal principal; + volatile Principal principal; @RequestMapping("/**") public Principal get(Principal principal) { 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 index 488274ef02..9863cebb72 100644 --- 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 @@ -16,6 +16,8 @@ package org.springframework.security.test.web.reactive.server; +import java.util.concurrent.ForkJoinPool; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.http.HttpHeaders; @@ -114,4 +116,54 @@ public class SecurityMockServerConfigurersAnnotatedTests extends AbstractMockSer assertPrincipalCreatedFromUserDetails(controller.removePrincipal(), userBuilder.build()); } + + @Test + @WithMockUser + public void withMockUserWhenOnMethodAndRequestIsExecutedOnDifferentThreadThenSuccess() { + Authentication authentication = TestSecurityContextHolder.getContext().getAuthentication(); + ForkJoinPool + .commonPool() + .submit(() -> + client + .get() + .exchange() + .expectStatus() + .isOk() + ) + .join(); + + controller.assertPrincipalIsEqualTo(authentication); + } + + @Test + @WithMockUser + public void withMockUserAndWithCallOnSeparateThreadWhenMutateWithMockPrincipalAndNoMutateThenOverridesAnnotationAndUsesAnnotation() { + TestingAuthenticationToken authentication = new TestingAuthenticationToken("authentication", "secret", "ROLE_USER"); + + ForkJoinPool + .commonPool() + .submit(() -> + client + .mutateWith(mockAuthentication(authentication)) + .get() + .exchange() + .expectStatus().isOk() + ) + .join(); + + controller.assertPrincipalIsEqualTo(authentication); + + + ForkJoinPool + .commonPool() + .submit(() -> + client + .get() + .exchange() + .expectStatus().isOk() + ) + .join(); + + assertPrincipalCreatedFromUserDetails(controller.removePrincipal(), userBuilder.build()); + } }