Ver Fonte

Allow WithSecurityContextTestExecutionListener to execute after @Before

Fixes: gh-2935
Rob Winch há 7 anos atrás
pai
commit
abae2f3e87

+ 38 - 0
docs/manual/src/docs/asciidoc/_includes/test.adoc

@@ -144,6 +144,15 @@ For example, the following would run every test with a user with the username "a
 public class WithMockUserTests {
 public class WithMockUserTests {
 ----
 ----
 
 
+By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
+This is the equivalent of happening before JUnit's `@Before`.
+You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked.
+
+[source,java]
+----
+@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)
+----
+
 
 
 [[test-method-withanonymoususer]]
 [[test-method-withanonymoususer]]
 === @WithAnonymousUser
 === @WithAnonymousUser
@@ -174,6 +183,15 @@ public class WithUserClassLevelAuthenticationTests {
 }
 }
 ----
 ----
 
 
+By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
+This is the equivalent of happening before JUnit's `@Before`.
+You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked.
+
+[source,java]
+----
+@WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)
+----
+
 
 
 [[test-method-withuserdetails]]
 [[test-method-withuserdetails]]
 === @WithUserDetails
 === @WithUserDetails
@@ -227,6 +245,16 @@ public void getMessageWithUserDetailsServiceBeanName() {
 Like `@WithMockUser` we can also place our annotation at the class level so that every test uses the same user.
 Like `@WithMockUser` we can also place our annotation at the class level so that every test uses the same user.
 However unlike `@WithMockUser`, `@WithUserDetails` requires the user to exist.
 However unlike `@WithMockUser`, `@WithUserDetails` requires the user to exist.
 
 
+By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
+This is the equivalent of happening before JUnit's `@Before`.
+You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked.
+
+[source,java]
+----
+@WithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION)
+----
+
+
 [[test-method-withsecuritycontext]]
 [[test-method-withsecuritycontext]]
 === @WithSecurityContext
 === @WithSecurityContext
 
 
@@ -301,6 +329,16 @@ final class WithUserDetailsSecurityContextFactory
 }
 }
 ----
 ----
 
 
+By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
+This is the equivalent of happening before JUnit's `@Before`.
+You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked.
+
+[source,java]
+----
+@WithSecurityContext(setupBefore = TestExecutionEvent.TEST_EXECUTION)
+----
+
+
 [[test-method-meta-annotations]]
 [[test-method-meta-annotations]]
 === Test Meta Annotations
 === Test Meta Annotations
 
 

+ 7 - 19
docs/manual/src/docs/asciidoc/index.adoc

@@ -400,28 +400,16 @@ git clone https://github.com/spring-projects/spring-security.git
 This will give you access to the entire project history (including all releases and branches) on your local machine.
 This will give you access to the entire project history (including all releases and branches) on your local machine.
 
 
 [[new]]
 [[new]]
-== What's New in Spring Security 5.0
-
-Spring Security 5.0 provides a number of new features as well as support for Spring Framework 5.
-In total there were 400+ enhancements and bugs resolved.
-You can find the change log at
-https://github.com/spring-projects/spring-security/milestone/90?closed=1[5.0.0.M1]
-https://github.com/spring-projects/spring-security/milestone/97?closed=1[5.0.0.M2]
-https://github.com/spring-projects/spring-security/milestone/100?closed=1[5.0.0.M3]
-https://github.com/spring-projects/spring-security/milestone/101?closed=1[5.0.0.M4]
-https://github.com/spring-projects/spring-security/milestone/102?closed=1[5.0.0.M5]
-https://github.com/spring-projects/spring-security/milestone/103?closed=1[5.0.0.RC1]
-https://github.com/spring-projects/spring-security/milestone/98?closed=1[5.0.0.RELEASE].
-Below are the highlights of this milestone release.
+== What's New in Spring Security 5.1
+
+Spring Security 5.1 provides a number of new features.
+Below are the highlights of the release.
 
 
 === New Features
 === New Features
 
 
-* <<jc-oauth2login,OAuth 2.0 Login>>
-* Reactive Support
-** <<jc-webflux,@EnableWebFluxSecurity>>
-** <<jc-erms,@EnableReactiveMethodSecurity>>
-** <<test-webflux,WebFlux Testing Support>>
-* Modernized <<core-services-password-encoding,Password Encoding>>
+* <<test-method>> - Support for customizing when the `SecurityContext` is setup in the test.
+For example, `@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)` will setup a user after JUnit's `@Before` and before the test executes.
+
 
 
 [[samples]]
 [[samples]]
 == Samples and Guides (Start Here)
 == Samples and Guides (Start Here)

+ 38 - 0
test/src/main/java/org/springframework/security/test/context/support/TestExecutionEvent.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-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.security.test.context.support;
+
+import org.springframework.test.context.TestContext;
+
+/**
+ * Represents the events on the methods of {@link org.springframework.test.context.TestExecutionListener}
+ *
+ * @author Rob Winch
+ * @since 5.1
+ */
+public enum TestExecutionEvent {
+	/**
+	 * Associated to {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
+	 * event.
+	 */
+	TEST_METHOD,
+	/**
+	 * Associated to {@link org.springframework.test.context.TestExecutionListener#beforeTestExecution(TestContext)}
+	 * event.
+	 */
+	TEST_EXECUTION
+}

+ 11 - 0
test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUser.java

@@ -22,8 +22,10 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.annotation.Target;
 
 
+import org.springframework.core.annotation.AliasFor;
 import org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContext;
+import org.springframework.test.context.TestContext;
 
 
 /**
 /**
  * When used with {@link WithSecurityContextTestExecutionListener} this
  * When used with {@link WithSecurityContextTestExecutionListener} this
@@ -58,4 +60,13 @@ import org.springframework.security.core.context.SecurityContext;
 @WithSecurityContext(factory = WithAnonymousUserSecurityContextFactory.class)
 @WithSecurityContext(factory = WithAnonymousUserSecurityContextFactory.class)
 public @interface WithAnonymousUser {
 public @interface WithAnonymousUser {
 
 
+	/**
+	 * Determines when the {@link SecurityContext} is setup. The default is before
+	 * {@link TestExecutionEvent#TEST_METHOD} which occurs during
+	 * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
+	 * @return the {@link TestExecutionEvent} to initialize before
+	 * @since 5.1
+	 */
+	@AliasFor(annotation = WithSecurityContext.class)
+	TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
 }
 }

+ 13 - 1
test/src/main/java/org/springframework/security/test/context/support/WithMockUser.java

@@ -22,10 +22,12 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.annotation.Target;
 
 
+import org.springframework.core.annotation.AliasFor;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.context.TestContext;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MockMvc;
 
 
 /**
 /**
@@ -102,4 +104,14 @@ public @interface WithMockUser {
 	 * @return
 	 * @return
 	 */
 	 */
 	String password() default "password";
 	String password() default "password";
-}
+
+	/**
+	 * Determines when the {@link SecurityContext} is setup. The default is before
+	 * {@link TestExecutionEvent#TEST_METHOD} which occurs during
+	 * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
+	 * @return the {@link TestExecutionEvent} to initialize before
+	 * @since 5.1
+	 */
+	@AliasFor(annotation = WithSecurityContext.class)
+	TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
+}

+ 12 - 1
test/src/main/java/org/springframework/security/test/context/support/WithSecurityContext.java

@@ -25,6 +25,7 @@ import java.lang.annotation.Target;
 
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContext;
+import org.springframework.test.context.TestContext;
 
 
 /**
 /**
  * <p>
  * <p>
@@ -61,4 +62,14 @@ public @interface WithSecurityContext {
 	 * @return
 	 * @return
 	 */
 	 */
 	Class<? extends WithSecurityContextFactory<? extends Annotation>> factory();
 	Class<? extends WithSecurityContextFactory<? extends Annotation>> factory();
-}
+
+	/**
+	 * Determines when the {@link SecurityContext} is setup. The default is before
+	 * {@link TestExecutionEvent#TEST_METHOD} which occurs during
+	 * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
+	 * @return the {@link TestExecutionEvent} to initialize before
+	 * @since 5.1
+	 */
+	TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
+
+}

+ 55 - 12
test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java

@@ -20,6 +20,7 @@ import java.lang.reflect.AnnotatedElement;
 
 
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.core.GenericTypeResolver;
 import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.annotation.AnnotatedElementUtils;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -46,6 +47,8 @@ import org.springframework.test.web.servlet.MockMvc;
 public class WithSecurityContextTestExecutionListener
 public class WithSecurityContextTestExecutionListener
 		extends AbstractTestExecutionListener {
 		extends AbstractTestExecutionListener {
 
 
+	static final String SECURITY_CONTEXT_ATTR_NAME = WithSecurityContextTestExecutionListener.class.getName().concat(".SECURITY_CONTEXT");
+
 	/**
 	/**
 	 * Sets up the {@link SecurityContext} for each test method. First the specific method
 	 * Sets up the {@link SecurityContext} for each test method. First the specific method
 	 * is inspected for a {@link WithSecurityContext} or {@link Annotation} that has
 	 * is inspected for a {@link WithSecurityContext} or {@link Annotation} that has
@@ -54,46 +57,68 @@ public class WithSecurityContextTestExecutionListener
 	 */
 	 */
 	@Override
 	@Override
 	public void beforeTestMethod(TestContext testContext) throws Exception {
 	public void beforeTestMethod(TestContext testContext) throws Exception {
-		SecurityContext securityContext = createSecurityContext(
+		TestSecurityContext testSecurityContext = createTestSecurityContext(
 				testContext.getTestMethod(), testContext);
 				testContext.getTestMethod(), testContext);
-		if (securityContext == null) {
-			securityContext = createSecurityContext(testContext.getTestClass(),
+		if (testSecurityContext == null) {
+			testSecurityContext = createTestSecurityContext(testContext.getTestClass(),
 					testContext);
 					testContext);
 		}
 		}
-		if (securityContext != null) {
+		if (testSecurityContext == null) {
+			return;
+		}
+
+		SecurityContext securityContext = testSecurityContext.securityContext;
+		if (testSecurityContext.getTestExecutionEvent() == TestExecutionEvent.TEST_METHOD) {
+			TestSecurityContextHolder.setContext(securityContext);
+		} else {
+			testContext.setAttribute(SECURITY_CONTEXT_ATTR_NAME, securityContext);
+		}
+	}
+
+	/**
+	 * If configured before test execution sets the SecurityContext
+	 * @since 5.1
+	 */
+	@Override
+	public void beforeTestExecution(TestContext testContext) {
+		SecurityContext securityContext = (SecurityContext) testContext.removeAttribute(SECURITY_CONTEXT_ATTR_NAME);
+		if(securityContext != null) {
 			TestSecurityContextHolder.setContext(securityContext);
 			TestSecurityContextHolder.setContext(securityContext);
 		}
 		}
 	}
 	}
 
 
-	private SecurityContext createSecurityContext(AnnotatedElement annotated,
+	private TestSecurityContext createTestSecurityContext(AnnotatedElement annotated,
 			TestContext context) {
 			TestContext context) {
-		WithSecurityContext withSecurityContext = AnnotationUtils
-				.findAnnotation(annotated, WithSecurityContext.class);
-		return createSecurityContext(annotated, withSecurityContext, context);
+		WithSecurityContext withSecurityContext = AnnotatedElementUtils
+				.findMergedAnnotation(annotated, WithSecurityContext.class);
+		return createTestSecurityContext(annotated, withSecurityContext, context);
 	}
 	}
 
 
-	private SecurityContext createSecurityContext(Class<?> annotated,
+	private TestSecurityContext createTestSecurityContext(Class<?> annotated,
 			TestContext context) {
 			TestContext context) {
 		MetaAnnotationUtils.AnnotationDescriptor<WithSecurityContext> withSecurityContextDescriptor = MetaAnnotationUtils
 		MetaAnnotationUtils.AnnotationDescriptor<WithSecurityContext> withSecurityContextDescriptor = MetaAnnotationUtils
 				.findAnnotationDescriptor(annotated, WithSecurityContext.class);
 				.findAnnotationDescriptor(annotated, WithSecurityContext.class);
 		WithSecurityContext withSecurityContext = withSecurityContextDescriptor == null
 		WithSecurityContext withSecurityContext = withSecurityContextDescriptor == null
 				? null : withSecurityContextDescriptor.getAnnotation();
 				? null : withSecurityContextDescriptor.getAnnotation();
-		return createSecurityContext(annotated, withSecurityContext, context);
+		return createTestSecurityContext(annotated, withSecurityContext, context);
 	}
 	}
 
 
 	@SuppressWarnings({ "rawtypes", "unchecked" })
 	@SuppressWarnings({ "rawtypes", "unchecked" })
-	private SecurityContext createSecurityContext(AnnotatedElement annotated,
+	private TestSecurityContext createTestSecurityContext(AnnotatedElement annotated,
 			WithSecurityContext withSecurityContext, TestContext context) {
 			WithSecurityContext withSecurityContext, TestContext context) {
 		if (withSecurityContext == null) {
 		if (withSecurityContext == null) {
 			return null;
 			return null;
 		}
 		}
+		withSecurityContext = AnnotationUtils
+			.synthesizeAnnotation(withSecurityContext, annotated);
 		WithSecurityContextFactory factory = createFactory(withSecurityContext, context);
 		WithSecurityContextFactory factory = createFactory(withSecurityContext, context);
 		Class<? extends Annotation> type = (Class<? extends Annotation>) GenericTypeResolver
 		Class<? extends Annotation> type = (Class<? extends Annotation>) GenericTypeResolver
 				.resolveTypeArgument(factory.getClass(),
 				.resolveTypeArgument(factory.getClass(),
 						WithSecurityContextFactory.class);
 						WithSecurityContextFactory.class);
 		Annotation annotation = findAnnotation(annotated, type);
 		Annotation annotation = findAnnotation(annotated, type);
+		TestExecutionEvent initialize = withSecurityContext.setupBefore();
 		try {
 		try {
-			return factory.createSecurityContext(annotation);
+			return new TestSecurityContext(factory.createSecurityContext(annotation), initialize);
 		}
 		}
 		catch (RuntimeException e) {
 		catch (RuntimeException e) {
 			throw new IllegalStateException(
 			throw new IllegalStateException(
@@ -150,4 +175,22 @@ public class WithSecurityContextTestExecutionListener
 	public int getOrder() {
 	public int getOrder() {
 		return 10000;
 		return 10000;
 	}
 	}
+
+	static class TestSecurityContext {
+		private final SecurityContext securityContext;
+		private final TestExecutionEvent testExecutionEvent;
+
+		TestSecurityContext(SecurityContext securityContext, TestExecutionEvent testExecutionEvent) {
+			this.securityContext = securityContext;
+			this.testExecutionEvent = testExecutionEvent;
+		}
+
+		public SecurityContext getSecurityContext() {
+			return this.securityContext;
+		}
+
+		public TestExecutionEvent getTestExecutionEvent() {
+			return this.testExecutionEvent;
+		}
+	}
 }
 }

+ 13 - 1
test/src/main/java/org/springframework/security/test/context/support/WithUserDetails.java

@@ -22,11 +22,13 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.annotation.Target;
 
 
+import org.springframework.core.annotation.AliasFor;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.test.context.TestContext;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MockMvc;
 
 
 /**
 /**
@@ -69,4 +71,14 @@ public @interface WithUserDetails {
 	 * @since 4.1
 	 * @since 4.1
 	 */
 	 */
 	String userDetailsServiceBeanName() default "";
 	String userDetailsServiceBeanName() default "";
-}
+
+	/**
+	 * Determines when the {@link SecurityContext} is setup. The default is before
+	 * {@link TestExecutionEvent#TEST_METHOD} which occurs during
+	 * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
+	 * @return the {@link TestExecutionEvent} to initialize before
+	 * @since 5.1
+	 */
+	@AliasFor(annotation = WithSecurityContext.class)
+	TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
+}

+ 65 - 0
test/src/test/java/org/springframework/security/test/context/support/WithAnonymousUserTests.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-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.security.test.context.support;
+
+import org.junit.Test;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+public class WithAnonymousUserTests {
+	@Test
+	public void defaults() {
+		WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(Annotated.class,
+			WithSecurityContext.class);
+
+		assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
+	}
+
+	@WithAnonymousUser
+	private class Annotated {
+	}
+
+	@Test
+	public void findMergedAnnotationWhenSetupExplicitThenOverridden() {
+		WithSecurityContext context = AnnotatedElementUtils
+			.findMergedAnnotation(SetupExplicit.class,
+				WithSecurityContext.class);
+
+		assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
+	}
+
+	@WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_METHOD)
+	private class SetupExplicit {
+	}
+
+	@Test
+	public void findMergedAnnotationWhenSetupOverriddenThenOverridden() {
+		WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(SetupOverridden.class,
+			WithSecurityContext.class);
+
+		assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION);
+	}
+
+	@WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)
+	private class SetupOverridden {
+	}
+}

+ 36 - 4
test/src/test/java/org/springframework/security/test/context/support/WithMockUserTests.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2018 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -13,26 +13,58 @@
  * See the License for the specific language governing permissions and
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * limitations under the License.
  */
  */
+
 package org.springframework.security.test.context.support;
 package org.springframework.security.test.context.support;
 
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThat;
 
 
 import org.junit.Test;
 import org.junit.Test;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
 
 
 public class WithMockUserTests {
 public class WithMockUserTests {
 
 
 	@Test
 	@Test
 	public void defaults() {
 	public void defaults() {
-		WithMockUser mockUser = AnnotationUtils.findAnnotation(Annotated.class,
+		WithMockUser mockUser = AnnotatedElementUtils.findMergedAnnotation(Annotated.class,
 				WithMockUser.class);
 				WithMockUser.class);
 		assertThat(mockUser.value()).isEqualTo("user");
 		assertThat(mockUser.value()).isEqualTo("user");
 		assertThat(mockUser.username()).isEmpty();
 		assertThat(mockUser.username()).isEmpty();
 		assertThat(mockUser.password()).isEqualTo("password");
 		assertThat(mockUser.password()).isEqualTo("password");
 		assertThat(mockUser.roles()).containsOnly("USER");
 		assertThat(mockUser.roles()).containsOnly("USER");
+		assertThat(mockUser.setupBefore()).isEqualByComparingTo(TestExecutionEvent.TEST_METHOD);
+
+		WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(Annotated.class,
+			WithSecurityContext.class);
+
+		assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
 	}
 	}
 
 
 	@WithMockUser
 	@WithMockUser
 	private class Annotated {
 	private class Annotated {
 	}
 	}
-}
+
+	@Test
+	public void findMergedAnnotationWhenSetupExplicitThenOverridden() {
+		WithSecurityContext context = AnnotatedElementUtils
+			.findMergedAnnotation(SetupExplicit.class,
+				WithSecurityContext.class);
+
+		assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
+	}
+
+	@WithMockUser(setupBefore = TestExecutionEvent.TEST_METHOD)
+	private class SetupExplicit {
+	}
+
+	@Test
+	public void findMergedAnnotationWhenSetupOverriddenThenOverridden() {
+		WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(SetupOverridden.class,
+			WithSecurityContext.class);
+
+		assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION);
+	}
+
+	@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)
+	private class SetupOverridden {
+	}
+}

+ 144 - 0
test/src/test/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListenerTests.java

@@ -0,0 +1,144 @@
+/*
+ * Copyright 2002-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.security.test.context.support;
+
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextImpl;
+import org.springframework.security.test.context.TestSecurityContextHolder;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.junit4.rules.SpringClassRule;
+import org.springframework.test.context.junit4.rules.SpringMethodRule;
+
+import java.lang.reflect.Method;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+@RunWith(MockitoJUnitRunner.class)
+@ContextConfiguration(classes = WithSecurityContextTestExecutionListenerTests.NoOpConfiguration.class)
+public class WithSecurityContextTestExecutionListenerTests {
+	@ClassRule
+	public static final SpringClassRule spring = new SpringClassRule();
+	@Rule
+	public final SpringMethodRule springMethod = new SpringMethodRule();
+
+	@Autowired
+	private ApplicationContext applicationContext;
+
+	@Mock
+	private TestContext testContext;
+
+	private WithSecurityContextTestExecutionListener listener = new WithSecurityContextTestExecutionListener();
+
+	@After
+	public void cleanup() {
+		TestSecurityContextHolder.clearContext();
+	}
+
+	@Test
+	public void beforeTestMethodWhenWithMockUserTestExecutionDefaultThenSecurityContextSet() throws Exception {
+		Method testMethod = TheTest.class.getMethod("withMockUserDefault");
+		when(this.testContext.getApplicationContext()).thenReturn(this.applicationContext);
+		when(this.testContext.getTestMethod()).thenReturn(testMethod);
+
+		this.listener.beforeTestMethod(this.testContext);
+
+		assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNotNull();
+		verify(this.testContext, never()).setAttribute(eq(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME), any(SecurityContext.class));
+	}
+
+	@Test
+	public void beforeTestMethodWhenWithMockUserTestMethodThenSecurityContextSet() throws Exception {
+		Method testMethod = TheTest.class.getMethod("withMockUserTestMethod");
+		when(this.testContext.getApplicationContext()).thenReturn(this.applicationContext);
+		when(this.testContext.getTestMethod()).thenReturn(testMethod);
+
+		this.listener.beforeTestMethod(this.testContext);
+
+		assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNotNull();
+		verify(this.testContext, never()).setAttribute(eq(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME), any(SecurityContext.class));
+	}
+
+	@Test
+	public void beforeTestMethodWhenWithMockUserTestExecutionThenTestContextSet() throws Exception {
+		Method testMethod = TheTest.class.getMethod("withMockUserTestExecution");
+		when(this.testContext.getApplicationContext()).thenReturn(this.applicationContext);
+		when(this.testContext.getTestMethod()).thenReturn(testMethod);
+
+		this.listener.beforeTestMethod(this.testContext);
+
+		assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNull();
+		verify(this.testContext).setAttribute(eq(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME), any(SecurityContext.class));
+	}
+
+	@Test
+	public void beforeTestExecutionWhenTestContextNullThenSecurityContextNotSet() throws Exception {
+		this.listener.beforeTestExecution(this.testContext);
+
+		assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNull();
+	}
+
+	@Test
+	public void beforeTestExecutionWhenTestContextNotNullThenSecurityContextSet() throws Exception {
+		SecurityContextImpl securityContext = new SecurityContextImpl();
+		securityContext.setAuthentication(new TestingAuthenticationToken("user", "passsword", "ROLE_USER"));
+		when(this.testContext.removeAttribute(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME)).thenReturn(securityContext);
+
+		this.listener.beforeTestExecution(this.testContext);
+
+		assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isEqualTo(securityContext.getAuthentication());
+	}
+
+	@Configuration
+	static class NoOpConfiguration {}
+
+	static class TheTest {
+		@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)
+		public void withMockUserTestExecution() {
+		}
+
+		@WithMockUser(setupBefore = TestExecutionEvent.TEST_METHOD)
+		public void withMockUserTestMethod() {
+		}
+
+		@WithMockUser
+		public void withMockUserDefault() {
+		}
+	}
+
+
+}

+ 34 - 1
test/src/test/java/org/springframework/security/test/context/support/WithUserDetailsTests.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2018 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.test.context.support;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThat;
 
 
 import org.junit.Test;
 import org.junit.Test;
+import org.springframework.core.annotation.AnnotatedElementUtils;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.core.annotation.AnnotationUtils;
 
 
 public class WithUserDetailsTests {
 public class WithUserDetailsTests {
@@ -27,9 +28,41 @@ public class WithUserDetailsTests {
 		WithUserDetails userDetails = AnnotationUtils.findAnnotation(Annotated.class,
 		WithUserDetails userDetails = AnnotationUtils.findAnnotation(Annotated.class,
 				WithUserDetails.class);
 				WithUserDetails.class);
 		assertThat(userDetails.value()).isEqualTo("user");
 		assertThat(userDetails.value()).isEqualTo("user");
+
+		WithSecurityContext context = AnnotatedElementUtils
+			.findMergedAnnotation(Annotated.class,
+				WithSecurityContext.class);
+
+		assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
 	}
 	}
 
 
 	@WithUserDetails
 	@WithUserDetails
 	private static class Annotated {
 	private static class Annotated {
 	}
 	}
+
+	@Test
+	public void findMergedAnnotationWhenSetupExplicitThenOverridden() {
+		WithSecurityContext context = AnnotatedElementUtils
+			.findMergedAnnotation(SetupExplicit.class,
+				WithSecurityContext.class);
+
+		assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
+	}
+
+	@WithUserDetails(setupBefore = TestExecutionEvent.TEST_METHOD)
+	private class SetupExplicit {
+	}
+
+	@Test
+	public void findMergedAnnotationWhenSetupOverriddenThenOverridden() {
+		WithSecurityContext context = AnnotatedElementUtils
+			.findMergedAnnotation(SetupOverridden.class,
+				WithSecurityContext.class);
+
+		assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION);
+	}
+
+	@WithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION)
+	private class SetupOverridden {
+	}
 }
 }