mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-24 19:28:45 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			706 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			706 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| [[test-method]]
 | |
| = Testing Method Security
 | |
| 
 | |
| This section demonstrates how to use Spring Security's Test support to test method-based security.
 | |
| We first introduce a `MessageService` that requires the user to be authenticated to be able to access it:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| public class HelloMessageService implements MessageService {
 | |
| 
 | |
| 	@PreAuthorize("authenticated")
 | |
| 	public String getMessage() {
 | |
| 		Authentication authentication = SecurityContextHolder.getContext()
 | |
| 			.getAuthentication();
 | |
| 		return "Hello " + authentication;
 | |
| 	}
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| class HelloMessageService : MessageService {
 | |
|     @PreAuthorize("authenticated")
 | |
|     fun getMessage(): String {
 | |
|         val authentication: Authentication = SecurityContextHolder.getContext().authentication
 | |
|         return "Hello $authentication"
 | |
|     }
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| The result of `getMessage` is a `String` that says "`Hello`" to the current Spring Security `Authentication`.
 | |
| The following listing shows example output:
 | |
| 
 | |
| [source,text]
 | |
| ----
 | |
| Hello org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ca25360: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
 | |
| ----
 | |
| 
 | |
| [[test-method-setup]]
 | |
| == Security Test Setup
 | |
| 
 | |
| Before we can use the Spring Security test support, we must perform some setup:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @ExtendWith(SpringExtension.class) // <1>
 | |
| @ContextConfiguration // <2>
 | |
| public class WithMockUserTests {
 | |
| 	// ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @ExtendWith(SpringExtension.class)
 | |
| @ContextConfiguration
 | |
| class WithMockUserTests {
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| <1> `@ExtendWith` instructs the spring-test module that it should create an `ApplicationContext`. For additional information, refer to the {spring-framework-reference-url}testing.html#testcontext-junit-jupiter-extension[Spring reference].
 | |
| <2> `@ContextConfiguration` instructs the spring-test the configuration to use to create the `ApplicationContext`. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the {spring-framework-reference-url}testing.html#spring-testing-annotation-contextconfiguration[Spring Reference].
 | |
| 
 | |
| [NOTE]
 | |
| ====
 | |
| Spring Security hooks into Spring Test support through the `WithSecurityContextTestExecutionListener`, which ensures that our tests are run with the correct user.
 | |
| It does this by populating the `SecurityContextHolder` prior to running our tests.
 | |
| If you use reactive method security, you also need `ReactorContextTestExecutionListener`, which populates `ReactiveSecurityContextHolder`.
 | |
| After the test is done, it clears out the `SecurityContextHolder`.
 | |
| If you need only Spring Security related support, you can replace `@ContextConfiguration` with `@SecurityTestExecutionListeners`.
 | |
| ====
 | |
| 
 | |
| Remember, we added the `@PreAuthorize` annotation to our `HelloMessageService`, so it requires an authenticated user to invoke it.
 | |
| If we run the tests, we expect the following test will pass:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Test(expected = AuthenticationCredentialsNotFoundException.class)
 | |
| public void getMessageUnauthenticated() {
 | |
| 	messageService.getMessage();
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Test(expected = AuthenticationCredentialsNotFoundException::class)
 | |
| fun getMessageUnauthenticated() {
 | |
|     messageService.getMessage()
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[test-method-withmockuser]]
 | |
| == @WithMockUser
 | |
| 
 | |
| The question is "How could we most easily run the test as a specific user?"
 | |
| The answer is to use `@WithMockUser`.
 | |
| The following test will be run as a user with the username "user", the password "password", and the roles "ROLE_USER".
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Test
 | |
| @WithMockUser
 | |
| public void getMessageWithMockUser() {
 | |
| String message = messageService.getMessage();
 | |
| ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Test
 | |
| @WithMockUser
 | |
| fun getMessageWithMockUser() {
 | |
|     val message: String = messageService.getMessage()
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Specifically the following is true:
 | |
| 
 | |
| * The user with a username of `user` does not have to exist, since we mock the user object.
 | |
| * The `Authentication` that is populated in the `SecurityContext` is of type `UsernamePasswordAuthenticationToken`.
 | |
| * The principal on the `Authentication` is Spring Security's `User` object.
 | |
| * The `User` has a username of `user`.
 | |
| * The `User` has a password of `password`.
 | |
| * A single `GrantedAuthority` named `ROLE_USER` is used.
 | |
| 
 | |
| The preceding example is handy, because it lets us use a lot of defaults.
 | |
| What if we wanted to run the test with a different username?
 | |
| The following test would run with a username of `customUser` (again, the user does not need to actually exist):
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Test
 | |
| @WithMockUser("customUsername")
 | |
| public void getMessageWithMockUserCustomUsername() {
 | |
| 	String message = messageService.getMessage();
 | |
| ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Test
 | |
| @WithMockUser("customUsername")
 | |
| fun getMessageWithMockUserCustomUsername() {
 | |
|     val message: String = messageService.getMessage()
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| We can also easily customize the roles.
 | |
| For example, the following test is invoked with a username of `admin` and roles of `ROLE_USER` and `ROLE_ADMIN`.
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Test
 | |
| @WithMockUser(username="admin",roles={"USER","ADMIN"})
 | |
| public void getMessageWithMockUserCustomUser() {
 | |
| 	String message = messageService.getMessage();
 | |
| 	...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Test
 | |
| @WithMockUser(username="admin",roles=["USER","ADMIN"])
 | |
| fun getMessageWithMockUserCustomUser() {
 | |
|     val message: String = messageService.getMessage()
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| If we do not want the value to automatically be prefixed with `ROLE_` we can use the `authorities` attribute.
 | |
| For example, the following test is invoked with a username of `admin` and the `USER` and `ADMIN` authorities.
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Test
 | |
| @WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
 | |
| public void getMessageWithMockUserCustomAuthorities() {
 | |
| 	String message = messageService.getMessage();
 | |
| 	...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Test
 | |
| @WithMockUser(username = "admin", authorities = ["ADMIN", "USER"])
 | |
| fun getMessageWithMockUserCustomUsername() {
 | |
|     val message: String = messageService.getMessage()
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| It can be a bit tedious to place the annotation on every test method.
 | |
| Instead, we can place the annotation at the class level. Then every test uses the specified user.
 | |
| The following example runs every test with a user whose username is `admin`, whose password is `password`, and who has the `ROLE_USER` and `ROLE_ADMIN` roles:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @ExtendWith(SpringExtension.class)
 | |
| @ContextConfiguration
 | |
| @WithMockUser(username="admin",roles={"USER","ADMIN"})
 | |
| public class WithMockUserTests {
 | |
| 	// ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @ExtendWith(SpringExtension.class)
 | |
| @ContextConfiguration
 | |
| @WithMockUser(username="admin",roles=["USER","ADMIN"])
 | |
| class WithMockUserTests {
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| If you use JUnit 5's `@Nested` test support, you can also place the annotation on the enclosing class to apply to all nested classes.
 | |
| The following example runs every test with a user whose username is `admin`, whose password is `password`, and who has the `ROLE_USER` and `ROLE_ADMIN` roles for both test methods.
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @ExtendWith(SpringExtension.class)
 | |
| @ContextConfiguration
 | |
| @WithMockUser(username="admin",roles={"USER","ADMIN"})
 | |
| public class WithMockUserTests {
 | |
| 
 | |
| 	@Nested
 | |
| 	public class TestSuite1 {
 | |
| 		// ... all test methods use admin user
 | |
| 	}
 | |
| 
 | |
| 	@Nested
 | |
| 	public class TestSuite2 {
 | |
| 		// ... all test methods use admin user
 | |
| 	}
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @ExtendWith(SpringExtension::class)
 | |
| @ContextConfiguration
 | |
| @WithMockUser(username = "admin", roles = ["USER", "ADMIN"])
 | |
| class WithMockUserTests {
 | |
|     @Nested
 | |
|     inner class TestSuite1 { // ... all test methods use admin user
 | |
|     }
 | |
| 
 | |
|     @Nested
 | |
|     inner class TestSuite2 { // ... all test methods use admin user
 | |
|     }
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| 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]]
 | |
| == @WithAnonymousUser
 | |
| 
 | |
| Using `@WithAnonymousUser` allows running as an anonymous user.
 | |
| This is especially convenient when you wish to run most of your tests with a specific user but want to run a few tests as an anonymous user.
 | |
| The following example runs `withMockUser1` and `withMockUser2` by using <<test-method-withmockuser,@WithMockUser>> and `anonymous` as an anonymous user:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @ExtendWith(SpringExtension.class)
 | |
| @WithMockUser
 | |
| public class WithUserClassLevelAuthenticationTests {
 | |
| 
 | |
| 	@Test
 | |
| 	public void withMockUser1() {
 | |
| 	}
 | |
| 
 | |
| 	@Test
 | |
| 	public void withMockUser2() {
 | |
| 	}
 | |
| 
 | |
| 	@Test
 | |
| 	@WithAnonymousUser
 | |
| 	public void anonymous() throws Exception {
 | |
| 		// override default to run as anonymous user
 | |
| 	}
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @ExtendWith(SpringExtension.class)
 | |
| @WithMockUser
 | |
| class WithUserClassLevelAuthenticationTests {
 | |
|     @Test
 | |
|     fun withMockUser1() {
 | |
|     }
 | |
| 
 | |
|     @Test
 | |
|     fun withMockUser2() {
 | |
|     }
 | |
| 
 | |
|     @Test
 | |
|     @WithAnonymousUser
 | |
|     fun anonymous() {
 | |
|         // override default to run as anonymous user
 | |
|     }
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| 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]]
 | |
| == @WithUserDetails
 | |
| 
 | |
| While `@WithMockUser` is a convenient way to get started, it may not work in all instances.
 | |
| For example, some applications expect the `Authentication` principal to be of a specific type.
 | |
| This is done so that the application can refer to the principal as the custom type and reduce coupling on Spring Security.
 | |
| 
 | |
| The custom principal is often returned by a custom `UserDetailsService` that returns an object that implements both `UserDetails` and the custom type.
 | |
| For situations like this, it is useful to create the test user by using a custom `UserDetailsService`.
 | |
| That is exactly what `@WithUserDetails` does.
 | |
| 
 | |
| Assuming we have a `UserDetailsService` exposed as a bean, the following test is invoked with an `Authentication` of type `UsernamePasswordAuthenticationToken` and a principal that is returned from the `UserDetailsService` with the username of `user`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Test
 | |
| @WithUserDetails
 | |
| public void getMessageWithUserDetails() {
 | |
| 	String message = messageService.getMessage();
 | |
| 	...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Test
 | |
| @WithUserDetails
 | |
| fun getMessageWithUserDetails() {
 | |
|     val message: String = messageService.getMessage()
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| We can also customize the username used to lookup the user from our `UserDetailsService`.
 | |
| For example, this test can be run with a principal that is returned from the `UserDetailsService` with the username of `customUsername`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Test
 | |
| @WithUserDetails("customUsername")
 | |
| public void getMessageWithUserDetailsCustomUsername() {
 | |
| 	String message = messageService.getMessage();
 | |
| 	...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Test
 | |
| @WithUserDetails("customUsername")
 | |
| fun getMessageWithUserDetailsCustomUsername() {
 | |
|     val message: String = messageService.getMessage()
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| We can also provide an explicit bean name to look up the `UserDetailsService`.
 | |
| The following test looks up the username of `customUsername` by using the `UserDetailsService` with a bean name of `myUserDetailsService`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Test
 | |
| @WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
 | |
| public void getMessageWithUserDetailsServiceBeanName() {
 | |
| 	String message = messageService.getMessage();
 | |
| 	...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Test
 | |
| @WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
 | |
| fun getMessageWithUserDetailsServiceBeanName() {
 | |
|     val message: String = messageService.getMessage()
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| As we did with `@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.
 | |
| 
 | |
| 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]]
 | |
| == @WithSecurityContext
 | |
| 
 | |
| We have seen that `@WithMockUser` is an excellent choice if we do not use a custom `Authentication` principal.
 | |
| Next, we discovered that `@WithUserDetails` lets us use a custom `UserDetailsService` to create our `Authentication` principal but requires the user to exist.
 | |
| We now see an option that allows the most flexibility.
 | |
| 
 | |
| We can create our own annotation that uses the `@WithSecurityContext` to create any `SecurityContext` we want.
 | |
| For example, we might create an annotation named `@WithMockCustomUser`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Retention(RetentionPolicy.RUNTIME)
 | |
| @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
 | |
| public @interface WithMockCustomUser {
 | |
| 
 | |
| 	String username() default "rob";
 | |
| 
 | |
| 	String name() default "Rob Winch";
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Retention(AnnotationRetention.RUNTIME)
 | |
| @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class)
 | |
| annotation class WithMockCustomUser(val username: String = "rob", val name: String = "Rob Winch")
 | |
| ----
 | |
| ======
 | |
| 
 | |
| You can see that `@WithMockCustomUser` is annotated with the `@WithSecurityContext` annotation.
 | |
| This is what signals to Spring Security test support that we intend to create a `SecurityContext` for the test.
 | |
| The `@WithSecurityContext` annotation requires that we specify a `SecurityContextFactory` to create a new `SecurityContext`, given our `@WithMockCustomUser` annotation.
 | |
| The following listing shows our `WithMockCustomUserSecurityContextFactory` implementation:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| public class WithMockCustomUserSecurityContextFactory
 | |
| 	implements WithSecurityContextFactory<WithMockCustomUser> {
 | |
| 	@Override
 | |
| 	public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
 | |
| 		SecurityContext context = SecurityContextHolder.createEmptyContext();
 | |
| 
 | |
| 		CustomUserDetails principal =
 | |
| 			new CustomUserDetails(customUser.name(), customUser.username());
 | |
| 		Authentication auth =
 | |
| 			UsernamePasswordAuthenticationToken.authenticated(principal, "password", principal.getAuthorities());
 | |
| 		context.setAuthentication(auth);
 | |
| 		return context;
 | |
| 	}
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory<WithMockCustomUser> {
 | |
|     override fun createSecurityContext(customUser: WithMockCustomUser): SecurityContext {
 | |
|         val context = SecurityContextHolder.createEmptyContext()
 | |
|         val principal = CustomUserDetails(customUser.name, customUser.username)
 | |
|         val auth: Authentication =
 | |
|             UsernamePasswordAuthenticationToken(principal, "password", principal.authorities)
 | |
|         context.authentication = auth
 | |
|         return context
 | |
|     }
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| We can now annotate a test class or a test method with our new annotation and Spring Security's `WithSecurityContextTestExecutionListener` to ensure that our `SecurityContext` is populated appropriately.
 | |
| 
 | |
| When creating your own `WithSecurityContextFactory` implementations, it is nice to know that they can be annotated with standard Spring annotations.
 | |
| For example, the `WithUserDetailsSecurityContextFactory` uses the `@Autowired` annotation to acquire the `UserDetailsService`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| final class WithUserDetailsSecurityContextFactory
 | |
| 	implements WithSecurityContextFactory<WithUserDetails> {
 | |
| 
 | |
| 	private UserDetailsService userDetailsService;
 | |
| 
 | |
| 	@Autowired
 | |
| 	public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
 | |
| 		this.userDetailsService = userDetailsService;
 | |
| 	}
 | |
| 
 | |
| 	public SecurityContext createSecurityContext(WithUserDetails withUser) {
 | |
| 		String username = withUser.value();
 | |
| 		Assert.hasLength(username, "value() must be non-empty String");
 | |
| 		UserDetails principal = userDetailsService.loadUserByUsername(username);
 | |
| 		Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(principal, principal.getPassword(), principal.getAuthorities());
 | |
| 		SecurityContext context = SecurityContextHolder.createEmptyContext();
 | |
| 		context.setAuthentication(authentication);
 | |
| 		return context;
 | |
| 	}
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| class WithUserDetailsSecurityContextFactory @Autowired constructor(private val userDetailsService: UserDetailsService) :
 | |
|     WithSecurityContextFactory<WithUserDetails> {
 | |
|     override fun createSecurityContext(withUser: WithUserDetails): SecurityContext {
 | |
|         val username: String = withUser.value
 | |
|         Assert.hasLength(username, "value() must be non-empty String")
 | |
|         val principal = userDetailsService.loadUserByUsername(username)
 | |
|         val authentication: Authentication =
 | |
|             UsernamePasswordAuthenticationToken(principal, principal.password, principal.authorities)
 | |
|         val context = SecurityContextHolder.createEmptyContext()
 | |
|         context.authentication = authentication
 | |
|         return context
 | |
|     }
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| 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 Meta Annotations
 | |
| 
 | |
| If you reuse the same user within your tests often, it is not ideal to have to repeatedly specify the attributes.
 | |
| For example, if you have many tests related to an administrative user with a username of `admin` and roles of `ROLE_USER` and `ROLE_ADMIN`, you have to write:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @WithMockUser(username="admin",roles={"USER","ADMIN"})
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @WithMockUser(username="admin",roles=["USER","ADMIN"])
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Rather than repeating this everywhere, we can use a meta annotation.
 | |
| For example, we could create a meta annotation named `WithMockAdmin`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Retention(RetentionPolicy.RUNTIME)
 | |
| @WithMockUser(value="rob",roles="ADMIN")
 | |
| public @interface WithMockAdmin { }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Retention(AnnotationRetention.RUNTIME)
 | |
| @WithMockUser(value = "rob", roles = ["ADMIN"])
 | |
| annotation class WithMockAdmin
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Now we can use `@WithMockAdmin` in the same way as the more verbose `@WithMockUser`.
 | |
| 
 | |
| Meta annotations work with any of the testing annotations described above.
 | |
| For example, this means we could create a meta annotation for `@WithUserDetails("admin")` as well.
 |