6 changed files with 898 additions and 899 deletions
@ -1,898 +0,0 @@
@@ -1,898 +0,0 @@
|
||||
[[test]] |
||||
= Testing |
||||
|
||||
This section describes the testing support provided by Spring Security. |
||||
|
||||
[TIP] |
||||
==== |
||||
To use the Spring Security test support, you must include `spring-security-test-{spring-security-version}.jar` as a dependency of your project. |
||||
==== |
||||
|
||||
[[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 in order to access it. |
||||
|
||||
[source,java] |
||||
---- |
||||
public class HelloMessageService implements MessageService { |
||||
|
||||
@PreAuthorize("authenticated") |
||||
public String getMessage() { |
||||
Authentication authentication = SecurityContextHolder.getContext() |
||||
.getAuthentication(); |
||||
return "Hello " + authentication; |
||||
} |
||||
} |
||||
---- |
||||
|
||||
The result of `getMessage` is a String saying "Hello" to the current Spring Security `Authentication`. |
||||
An example of the output is displayed below. |
||||
|
||||
[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 Spring Security Test support, we must perform some setup. An example can be seen below: |
||||
|
||||
[source,java] |
||||
---- |
||||
@RunWith(SpringJUnit4ClassRunner.class) // <1> |
||||
@ContextConfiguration // <2> |
||||
public class WithMockUserTests { |
||||
---- |
||||
|
||||
This is a basic example of how to setup Spring Security Test. The highlights are: |
||||
|
||||
<1> `@RunWith` instructs the spring-test module that it should create an `ApplicationContext`. This is no different than using the existing Spring Test support. For additional information, refer to the http://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#integration-testing-annotations-standard[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 http://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#testcontext-ctx-management[Spring Reference] |
||||
|
||||
NOTE: Spring Security hooks into Spring Test support using the `WithSecurityContextTestExecutionListener` which will ensure our tests are ran with the correct user. |
||||
It does this by populating the `SecurityContextHolder` prior to running our tests. |
||||
After the test is done, it will clear out the `SecurityContextHolder`. |
||||
If you only need Spring Security related support, you can replace `@ContextConfiguration` with `@SecurityTestExecutionListeners`. |
||||
|
||||
Remember we added the `@PreAuthorize` annotation to our `HelloMessageService` and so it requires an authenticated user to invoke it. |
||||
If we ran the following test, we would expect the following test will pass: |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test(expected = AuthenticationCredentialsNotFoundException.class) |
||||
public void 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". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser |
||||
public void getMessageWithMockUser() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
Specifically the following is true: |
||||
|
||||
* The user with the username "user" does not have to exist since we are mocking the user |
||||
* 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` will have the username of "user", the password "password", and a single `GrantedAuthority` named "ROLE_USER" is used. |
||||
|
||||
Our example is nice because we are able to leverage a lot of defaults. |
||||
What if we wanted to run the test with a different username? |
||||
The following test would run with the username "customUser". Again, the user does not need to actually exist. |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser("customUsername") |
||||
public void getMessageWithMockUserCustomUsername() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
We can also easily customize the roles. |
||||
For example, this test will be invoked with the username "admin" and the roles "ROLE_USER" and "ROLE_ADMIN". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"}) |
||||
public void getMessageWithMockUserCustomUser() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
If we do not want the value to automatically be prefixed with ROLE_ we can leverage the authorities attribute. |
||||
For example, this test will be invoked with the username "admin" and the authorities "USER" and "ADMIN". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" }) |
||||
public void getMessageWithMockUserCustomAuthorities() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
Of course it can be a bit tedious placing the annotation on every test method. |
||||
Instead, we can place the annotation at the class level and every test will use the specified user. |
||||
For example, the following would run every test with a user with the username "admin", the password "password", and the roles "ROLE_USER" and "ROLE_ADMIN". |
||||
|
||||
[source,java] |
||||
---- |
||||
@RunWith(SpringJUnit4ClassRunner.class) |
||||
@ContextConfiguration |
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"}) |
||||
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]] |
||||
=== @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. |
||||
For example, the following will run withMockUser1 and withMockUser2 using <<test-method-withmockuser,@WithMockUser>> and anonymous as an anonymous user. |
||||
|
||||
[source,java] |
||||
---- |
||||
@RunWith(SpringJUnit4ClassRunner.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 |
||||
} |
||||
} |
||||
---- |
||||
|
||||
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 very convenient way to get started, it may not work in all instances. |
||||
For example, it is common for applications to expect that the `Authentication` principal 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 times 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 using the custom `UserDetailsService`. |
||||
That is exactly what `@WithUserDetails` does. |
||||
|
||||
Assuming we have a `UserDetailsService` exposed as a bean, the following test will be invoked with an `Authentication` of type `UsernamePasswordAuthenticationToken` and a principal that is returned from the `UserDetailsService` with the username of "user". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithUserDetails |
||||
public void getMessageWithUserDetails() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
We can also customize the username used to lookup the user from our `UserDetailsService`. |
||||
For example, this test would be executed with a principal that is returned from the `UserDetailsService` with the username of "customUsername". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithUserDetails("customUsername") |
||||
public void getMessageWithUserDetailsCustomUsername() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
We can also provide an explicit bean name to look up the `UserDetailsService`. |
||||
For example, this test would look up the username of "customUsername" using the `UserDetailsService` with the bean name "myUserDetailsService". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService") |
||||
public void getMessageWithUserDetailsServiceBeanName() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
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. |
||||
|
||||
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 are not using a custom `Authentication` principal. |
||||
Next we discovered that `@WithUserDetails` would allow us to use a custom `UserDetailsService` to create our `Authentication` principal but required the user to exist. |
||||
We will 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` as shown below: |
||||
|
||||
[source,java] |
||||
---- |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) |
||||
public @interface WithMockCustomUser { |
||||
|
||||
String username() default "rob"; |
||||
|
||||
String name() default "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 we specify a `SecurityContextFactory` that will create a new `SecurityContext` given our `@WithMockCustomUser` annotation. |
||||
You can find our `WithMockCustomUserSecurityContextFactory` implementation below: |
||||
|
||||
[source,java] |
||||
---- |
||||
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 = |
||||
new UsernamePasswordAuthenticationToken(principal, "password", principal.getAuthorities()); |
||||
context.setAuthentication(auth); |
||||
return context; |
||||
} |
||||
} |
||||
---- |
||||
|
||||
We can now annotate a test class or a test method with our new annotation and Spring Security's `WithSecurityContextTestExecutionListener` will 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`: |
||||
|
||||
[source,java] |
||||
---- |
||||
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 = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities()); |
||||
SecurityContext context = SecurityContextHolder.createEmptyContext(); |
||||
context.setAuthentication(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 there are many tests related to an administrative user with the username "admin" and the roles `ROLE_USER` and `ROLE_ADMIN` you would have to write: |
||||
|
||||
[source,java] |
||||
---- |
||||
@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`: |
||||
|
||||
[source,java] |
||||
---- |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@WithMockUser(value="rob",roles="ADMIN") |
||||
public @interface 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. |
||||
|
||||
|
||||
[[test-mockmvc]] |
||||
== Spring MVC Test Integration |
||||
|
||||
Spring Security provides comprehensive integration with http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#spring-mvc-test-framework[Spring MVC Test] |
||||
|
||||
[[test-mockmvc-setup]] |
||||
=== Setting Up MockMvc and Spring Security |
||||
|
||||
In order to use Spring Security with Spring MVC Test it is necessary to add the Spring Security `FilterChainProxy` as a `Filter`. |
||||
It is also necessary to add Spring Security's `TestSecurityContextHolderPostProcessor` to support <<Running as a User in Spring MVC Test with Annotations,Running as a User in Spring MVC Test with Annotations>>. |
||||
This can be done using Spring Security's `SecurityMockMvcConfigurers.springSecurity()`. |
||||
For example: |
||||
|
||||
NOTE: Spring Security's testing support requires spring-test-4.1.3.RELEASE or greater. |
||||
|
||||
[source,java] |
||||
---- |
||||
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*; |
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class) |
||||
@ContextConfiguration |
||||
@WebAppConfiguration |
||||
public class CsrfShowcaseTests { |
||||
|
||||
@Autowired |
||||
private WebApplicationContext context; |
||||
|
||||
private MockMvc mvc; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
mvc = MockMvcBuilders |
||||
.webAppContextSetup(context) |
||||
.apply(springSecurity()) // <1> |
||||
.build(); |
||||
} |
||||
|
||||
... |
||||
---- |
||||
|
||||
<1> `SecurityMockMvcConfigurers.springSecurity()` will perform all of the initial setup we need to integrate Spring Security with Spring MVC Test |
||||
|
||||
[[test-mockmvc-smmrpp]] |
||||
=== SecurityMockMvcRequestPostProcessors |
||||
|
||||
Spring MVC Test provides a convenient interface called a `RequestPostProcessor` that can be used to modify a request. |
||||
Spring Security provides a number of `RequestPostProcessor` implementations that make testing easier. |
||||
In order to use Spring Security's `RequestPostProcessor` implementations ensure the following static import is used: |
||||
|
||||
[source,java] |
||||
---- |
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; |
||||
---- |
||||
|
||||
[[test-mockmvc-csrf]] |
||||
==== Testing with CSRF Protection |
||||
|
||||
When testing any non-safe HTTP methods and using Spring Security's CSRF protection, you must be sure to include a valid CSRF Token in the request. |
||||
To specify a valid CSRF token as a request parameter using the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(post("/").with(csrf())) |
||||
---- |
||||
|
||||
If you like you can include CSRF token in the header instead: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(post("/").with(csrf().asHeader())) |
||||
---- |
||||
|
||||
You can also test providing an invalid CSRF token using the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(post("/").with(csrf().useInvalidToken())) |
||||
---- |
||||
|
||||
[[test-mockmvc-securitycontextholder]] |
||||
==== Running a Test as a User in Spring MVC Test |
||||
|
||||
It is often desirable to run tests as a specific user. |
||||
There are two simple ways of populating the user: |
||||
|
||||
* <<Running as a User in Spring MVC Test with RequestPostProcessor,Running as a User in Spring MVC Test with RequestPostProcessor>> |
||||
* <<Running as a User in Spring MVC Test with Annotations,Running as a User in Spring MVC Test with Annotations>> |
||||
|
||||
[[test-mockmvc-securitycontextholder-rpp]] |
||||
==== Running as a User in Spring MVC Test with RequestPostProcessor |
||||
|
||||
There are a number of options available to associate a user to the current `HttpServletRequest`. |
||||
For example, the following will run as a user (which does not need to exist) with the username "user", the password "password", and the role "ROLE_USER": |
||||
|
||||
[NOTE] |
||||
==== |
||||
The support works by associating the user to the `HttpServletRequest`. |
||||
To associate the request to the `SecurityContextHolder` you need to ensure that the `SecurityContextPersistenceFilter` is associated with the `MockMvc` instance. |
||||
A few ways to do this are: |
||||
|
||||
* Invoking <<test-mockmvc-setup,apply(springSecurity())>> |
||||
* Adding Spring Security's `FilterChainProxy` to `MockMvc` |
||||
* Manually adding `SecurityContextPersistenceFilter` to the `MockMvc` instance may make sense when using `MockMvcBuilders.standaloneSetup` |
||||
==== |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(user("user"))) |
||||
---- |
||||
|
||||
You can easily make customizations. |
||||
For example, the following will run as a user (which does not need to exist) with the username "admin", the password "pass", and the roles "ROLE_USER" and "ROLE_ADMIN". |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/admin").with(user("admin").password("pass").roles("USER","ADMIN"))) |
||||
---- |
||||
|
||||
If you have a custom `UserDetails` that you would like to use, you can easily specify that as well. |
||||
For example, the following will use the specified `UserDetails` (which does not need to exist) to run with a `UsernamePasswordAuthenticationToken` that has a principal of the specified `UserDetails`: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(user(userDetails))) |
||||
---- |
||||
|
||||
You can run as anonymous user using the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(anonymous())) |
||||
---- |
||||
|
||||
This is especially useful if you are running with a default user and wish to execute a few requests as an anonymous user. |
||||
|
||||
If you want a custom `Authentication` (which does not need to exist) you can do so using the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(authentication(authentication))) |
||||
---- |
||||
|
||||
You can even customize the `SecurityContext` using the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(securityContext(securityContext))) |
||||
---- |
||||
|
||||
We can also ensure to run as a specific user for every request by using ``MockMvcBuilders``'s default request. |
||||
For example, the following will run as a user (which does not need to exist) with the username "admin", the password "password", and the role "ROLE_ADMIN": |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc = MockMvcBuilders |
||||
.webAppContextSetup(context) |
||||
.defaultRequest(get("/").with(user("user").roles("ADMIN"))) |
||||
.apply(springSecurity()) |
||||
.build(); |
||||
---- |
||||
|
||||
If you find you are using the same user in many of your tests, it is recommended to move the user to a method. |
||||
For example, you can specify the following in your own class named `CustomSecurityMockMvcRequestPostProcessors`: |
||||
|
||||
[source,java] |
||||
---- |
||||
public static RequestPostProcessor rob() { |
||||
return user("rob").roles("ADMIN"); |
||||
} |
||||
---- |
||||
|
||||
Now you can perform a static import on `SecurityMockMvcRequestPostProcessors` and use that within your tests: |
||||
|
||||
[source,java] |
||||
---- |
||||
import static sample.CustomSecurityMockMvcRequestPostProcessors.*; |
||||
|
||||
... |
||||
|
||||
mvc |
||||
.perform(get("/").with(rob())) |
||||
---- |
||||
|
||||
===== Running as a User in Spring MVC Test with Annotations |
||||
|
||||
As an alternative to using a `RequestPostProcessor` to create your user, you can use annotations described in <<Testing Method Security>>. |
||||
For example, the following will run the test with the user with username "user", password "password", and role "ROLE_USER": |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser |
||||
public void requestProtectedUrlWithUser() throws Exception { |
||||
mvc |
||||
.perform(get("/")) |
||||
... |
||||
} |
||||
---- |
||||
|
||||
Alternatively, the following will run the test with the user with username "user", password "password", and role "ROLE_ADMIN": |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser(roles="ADMIN") |
||||
public void requestProtectedUrlWithUser() throws Exception { |
||||
mvc |
||||
.perform(get("/")) |
||||
... |
||||
} |
||||
---- |
||||
|
||||
==== Testing HTTP Basic Authentication |
||||
|
||||
While it has always been possible to authenticate with HTTP Basic, it was a bit tedious to remember the header name, format, and encode the values. |
||||
Now this can be done using Spring Security's `httpBasic` `RequestPostProcessor`. |
||||
For example, the snippet below: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(httpBasic("user","password"))) |
||||
---- |
||||
|
||||
will attempt to use HTTP Basic to authenticate a user with the username "user" and the password "password" by ensuring the following header is populated on the HTTP Request: |
||||
|
||||
[source,text] |
||||
---- |
||||
Authorization: Basic dXNlcjpwYXNzd29yZA== |
||||
---- |
||||
|
||||
=== SecurityMockMvcRequestBuilders |
||||
|
||||
Spring MVC Test also provides a `RequestBuilder` interface that can be used to create the `MockHttpServletRequest` used in your test. |
||||
Spring Security provides a few `RequestBuilder` implementations that can be used to make testing easier. |
||||
In order to use Spring Security's `RequestBuilder` implementations ensure the following static import is used: |
||||
|
||||
[source,java] |
||||
---- |
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*; |
||||
---- |
||||
|
||||
==== Testing Form Based Authentication |
||||
|
||||
You can easily create a request to test a form based authentication using Spring Security's testing support. |
||||
For example, the following will submit a POST to "/login" with the username "user", the password "password", and a valid CSRF token: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin()) |
||||
---- |
||||
|
||||
It is easy to customize the request. |
||||
For example, the following will submit a POST to "/auth" with the username "admin", the password "pass", and a valid CSRF token: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin("/auth").user("admin").password("pass")) |
||||
---- |
||||
|
||||
We can also customize the parameters names that the username and password are included on. |
||||
For example, this is the above request modified to include the username on the HTTP parameter "u" and the password on the HTTP parameter "p". |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin("/auth").user("u","admin").password("p","pass")) |
||||
---- |
||||
|
||||
[[test-logout]] |
||||
==== Testing Logout |
||||
|
||||
While fairly trivial using standard Spring MVC Test, you can use Spring Security's testing support to make testing log out easier. |
||||
For example, the following will submit a POST to "/logout" with a valid CSRF token: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(logout()) |
||||
---- |
||||
|
||||
You can also customize the URL to post to. |
||||
For example, the snippet below will submit a POST to "/signout" with a valid CSRF token: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(logout("/signout")) |
||||
---- |
||||
|
||||
=== SecurityMockMvcResultMatchers |
||||
|
||||
At times it is desirable to make various security related assertions about a request. |
||||
To accommodate this need, Spring Security Test support implements Spring MVC Test's `ResultMatcher` interface. |
||||
In order to use Spring Security's `ResultMatcher` implementations ensure the following static import is used: |
||||
|
||||
[source,java] |
||||
---- |
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*; |
||||
---- |
||||
|
||||
==== Unauthenticated Assertion |
||||
|
||||
At times it may be valuable to assert that there is no authenticated user associated with the result of a `MockMvc` invocation. |
||||
For example, you might want to test submitting an invalid username and password and verify that no user is authenticated. |
||||
You can easily do this with Spring Security's testing support using something like the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin().password("invalid")) |
||||
.andExpect(unauthenticated()); |
||||
---- |
||||
|
||||
==== Authenticated Assertion |
||||
|
||||
It is often times that we must assert that an authenticated user exists. |
||||
For example, we may want to verify that we authenticated successfully. |
||||
We could verify that a form based login was successful with the following snippet of code: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin()) |
||||
.andExpect(authenticated()); |
||||
---- |
||||
|
||||
If we wanted to assert the roles of the user, we could refine our previous code as shown below: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin().user("admin")) |
||||
.andExpect(authenticated().withRoles("USER","ADMIN")); |
||||
---- |
||||
|
||||
Alternatively, we could verify the username: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin().user("admin")) |
||||
.andExpect(authenticated().withUsername("admin")); |
||||
---- |
||||
|
||||
We can also combine the assertions: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin().user("admin").roles("USER","ADMIN")) |
||||
.andExpect(authenticated().withUsername("admin")); |
||||
---- |
||||
|
||||
We can also make arbitrary assertions on the authentication |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin()) |
||||
.andExpect(authenticated().withAuthentication(auth -> |
||||
assertThat(auth).isInstanceOf(UsernamePasswordAuthenticationToken.class))); |
||||
---- |
||||
|
||||
[[test-webflux]] |
||||
== WebFlux Support |
||||
|
||||
Spring Security provides test integration with Spring WebFlux for both method security and WebFlux. |
||||
You can find a complete working sample at {gh-samples-url}/javaconfig/hellowebflux-method[hellowebflux-method] |
||||
|
||||
|
||||
[[test-erms]] |
||||
=== Reactive Method Security |
||||
|
||||
For example, we can test our example from <<jc-erms>> using the same setup and annotations we did in <<test-method>>. |
||||
Here is a minimal sample of what we can do: |
||||
|
||||
[source,java] |
||||
---- |
||||
@RunWith(SpringRunner.class) |
||||
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class) |
||||
public class HelloWorldMessageServiceTests { |
||||
@Autowired |
||||
HelloWorldMessageService messages; |
||||
|
||||
@Test |
||||
public void messagesWhenNotAuthenticatedThenDenied() { |
||||
StepVerifier.create(this.messages.findMessage()) |
||||
.expectError(AccessDeniedException.class) |
||||
.verify(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
public void messagesWhenUserThenDenied() { |
||||
StepVerifier.create(this.messages.findMessage()) |
||||
.expectError(AccessDeniedException.class) |
||||
.verify(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser(roles = "ADMIN") |
||||
public void messagesWhenAdminThenOk() { |
||||
StepVerifier.create(this.messages.findMessage()) |
||||
.expectNext("Hello World!") |
||||
.verifyComplete(); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
[[test-webtestclient]] |
||||
=== WebTestClientSupport |
||||
|
||||
Spring Security provides integration with `WebTestClient`. |
||||
The basic setup looks like this: |
||||
|
||||
[source,java] |
||||
---- |
||||
@RunWith(SpringRunner.class) |
||||
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class) |
||||
public class HelloWebfluxMethodApplicationTests { |
||||
@Autowired |
||||
ApplicationContext context; |
||||
|
||||
WebTestClient rest; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
this.rest = WebTestClient |
||||
.bindToApplicationContext(this.context) |
||||
// add Spring Security test Support |
||||
.apply(springSecurity()) |
||||
.configureClient() |
||||
.filter(basicAuthentication()) |
||||
.build(); |
||||
} |
||||
// ... |
||||
} |
||||
---- |
||||
|
||||
==== Authentication |
||||
|
||||
After applying the Spring Security support to `WebTestClient` we can use either annotations or `mutateWith` support. |
||||
For example: |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
public void messageWhenNotAuthenticated() throws Exception { |
||||
this.rest |
||||
.get() |
||||
.uri("/message") |
||||
.exchange() |
||||
.expectStatus().isUnauthorized(); |
||||
} |
||||
|
||||
// --- WithMockUser --- |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
public void messageWhenWithMockUserThenForbidden() throws Exception { |
||||
this.rest |
||||
.get() |
||||
.uri("/message") |
||||
.exchange() |
||||
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser(roles = "ADMIN") |
||||
public void messageWhenWithMockAdminThenOk() throws Exception { |
||||
this.rest |
||||
.get() |
||||
.uri("/message") |
||||
.exchange() |
||||
.expectStatus().isOk() |
||||
.expectBody(String.class).isEqualTo("Hello World!"); |
||||
} |
||||
|
||||
// --- mutateWith mockUser --- |
||||
|
||||
@Test |
||||
public void messageWhenMutateWithMockUserThenForbidden() throws Exception { |
||||
this.rest |
||||
.mutateWith(mockUser()) |
||||
.get() |
||||
.uri("/message") |
||||
.exchange() |
||||
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN); |
||||
} |
||||
|
||||
@Test |
||||
public void messageWhenMutateWithMockAdminThenOk() throws Exception { |
||||
this.rest |
||||
.mutateWith(mockUser().roles("ADMIN")) |
||||
.get() |
||||
.uri("/message") |
||||
.exchange() |
||||
.expectStatus().isOk() |
||||
.expectBody(String.class).isEqualTo("Hello World!"); |
||||
} |
||||
---- |
||||
|
||||
|
||||
==== CSRF Support |
||||
|
||||
Spring Security also provides support for CSRF testing with `WebTestClient`. |
||||
For example: |
||||
|
||||
[source,java] |
||||
---- |
||||
this.rest |
||||
// provide a valid CSRF token |
||||
.mutateWith(csrf()) |
||||
.post() |
||||
.uri("/login") |
||||
... |
||||
---- |
||||
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
[[test]] |
||||
= Testing |
||||
|
||||
This section describes the testing support provided by Spring Security. |
||||
|
||||
[TIP] |
||||
==== |
||||
To use the Spring Security test support, you must include `spring-security-test-{spring-security-version}.jar` as a dependency of your project. |
||||
==== |
||||
|
||||
include::method.adoc[] |
||||
|
||||
include::mockmvc.adoc[] |
||||
|
||||
include::webtestclient.adoc[] |
||||
@ -0,0 +1,356 @@
@@ -0,0 +1,356 @@
|
||||
[[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 in order to access it. |
||||
|
||||
[source,java] |
||||
---- |
||||
public class HelloMessageService implements MessageService { |
||||
|
||||
@PreAuthorize("authenticated") |
||||
public String getMessage() { |
||||
Authentication authentication = SecurityContextHolder.getContext() |
||||
.getAuthentication(); |
||||
return "Hello " + authentication; |
||||
} |
||||
} |
||||
---- |
||||
|
||||
The result of `getMessage` is a String saying "Hello" to the current Spring Security `Authentication`. |
||||
An example of the output is displayed below. |
||||
|
||||
[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 Spring Security Test support, we must perform some setup. An example can be seen below: |
||||
|
||||
[source,java] |
||||
---- |
||||
@RunWith(SpringJUnit4ClassRunner.class) // <1> |
||||
@ContextConfiguration // <2> |
||||
public class WithMockUserTests { |
||||
---- |
||||
|
||||
This is a basic example of how to setup Spring Security Test. The highlights are: |
||||
|
||||
<1> `@RunWith` instructs the spring-test module that it should create an `ApplicationContext`. This is no different than using the existing Spring Test support. For additional information, refer to the http://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#integration-testing-annotations-standard[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 http://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#testcontext-ctx-management[Spring Reference] |
||||
|
||||
NOTE: Spring Security hooks into Spring Test support using the `WithSecurityContextTestExecutionListener` which will ensure our tests are ran with the correct user. |
||||
It does this by populating the `SecurityContextHolder` prior to running our tests. |
||||
After the test is done, it will clear out the `SecurityContextHolder`. |
||||
If you only need Spring Security related support, you can replace `@ContextConfiguration` with `@SecurityTestExecutionListeners`. |
||||
|
||||
Remember we added the `@PreAuthorize` annotation to our `HelloMessageService` and so it requires an authenticated user to invoke it. |
||||
If we ran the following test, we would expect the following test will pass: |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test(expected = AuthenticationCredentialsNotFoundException.class) |
||||
public void 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". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser |
||||
public void getMessageWithMockUser() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
Specifically the following is true: |
||||
|
||||
* The user with the username "user" does not have to exist since we are mocking the user |
||||
* 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` will have the username of "user", the password "password", and a single `GrantedAuthority` named "ROLE_USER" is used. |
||||
|
||||
Our example is nice because we are able to leverage a lot of defaults. |
||||
What if we wanted to run the test with a different username? |
||||
The following test would run with the username "customUser". Again, the user does not need to actually exist. |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser("customUsername") |
||||
public void getMessageWithMockUserCustomUsername() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
We can also easily customize the roles. |
||||
For example, this test will be invoked with the username "admin" and the roles "ROLE_USER" and "ROLE_ADMIN". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"}) |
||||
public void getMessageWithMockUserCustomUser() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
If we do not want the value to automatically be prefixed with ROLE_ we can leverage the authorities attribute. |
||||
For example, this test will be invoked with the username "admin" and the authorities "USER" and "ADMIN". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" }) |
||||
public void getMessageWithMockUserCustomAuthorities() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
Of course it can be a bit tedious placing the annotation on every test method. |
||||
Instead, we can place the annotation at the class level and every test will use the specified user. |
||||
For example, the following would run every test with a user with the username "admin", the password "password", and the roles "ROLE_USER" and "ROLE_ADMIN". |
||||
|
||||
[source,java] |
||||
---- |
||||
@RunWith(SpringJUnit4ClassRunner.class) |
||||
@ContextConfiguration |
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"}) |
||||
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]] |
||||
=== @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. |
||||
For example, the following will run withMockUser1 and withMockUser2 using <<test-method-withmockuser,@WithMockUser>> and anonymous as an anonymous user. |
||||
|
||||
[source,java] |
||||
---- |
||||
@RunWith(SpringJUnit4ClassRunner.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 |
||||
} |
||||
} |
||||
---- |
||||
|
||||
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 very convenient way to get started, it may not work in all instances. |
||||
For example, it is common for applications to expect that the `Authentication` principal 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 times 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 using the custom `UserDetailsService`. |
||||
That is exactly what `@WithUserDetails` does. |
||||
|
||||
Assuming we have a `UserDetailsService` exposed as a bean, the following test will be invoked with an `Authentication` of type `UsernamePasswordAuthenticationToken` and a principal that is returned from the `UserDetailsService` with the username of "user". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithUserDetails |
||||
public void getMessageWithUserDetails() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
We can also customize the username used to lookup the user from our `UserDetailsService`. |
||||
For example, this test would be executed with a principal that is returned from the `UserDetailsService` with the username of "customUsername". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithUserDetails("customUsername") |
||||
public void getMessageWithUserDetailsCustomUsername() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
We can also provide an explicit bean name to look up the `UserDetailsService`. |
||||
For example, this test would look up the username of "customUsername" using the `UserDetailsService` with the bean name "myUserDetailsService". |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService") |
||||
public void getMessageWithUserDetailsServiceBeanName() { |
||||
String message = messageService.getMessage(); |
||||
... |
||||
} |
||||
---- |
||||
|
||||
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. |
||||
|
||||
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 are not using a custom `Authentication` principal. |
||||
Next we discovered that `@WithUserDetails` would allow us to use a custom `UserDetailsService` to create our `Authentication` principal but required the user to exist. |
||||
We will 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` as shown below: |
||||
|
||||
[source,java] |
||||
---- |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) |
||||
public @interface WithMockCustomUser { |
||||
|
||||
String username() default "rob"; |
||||
|
||||
String name() default "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 we specify a `SecurityContextFactory` that will create a new `SecurityContext` given our `@WithMockCustomUser` annotation. |
||||
You can find our `WithMockCustomUserSecurityContextFactory` implementation below: |
||||
|
||||
[source,java] |
||||
---- |
||||
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 = |
||||
new UsernamePasswordAuthenticationToken(principal, "password", principal.getAuthorities()); |
||||
context.setAuthentication(auth); |
||||
return context; |
||||
} |
||||
} |
||||
---- |
||||
|
||||
We can now annotate a test class or a test method with our new annotation and Spring Security's `WithSecurityContextTestExecutionListener` will 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`: |
||||
|
||||
[source,java] |
||||
---- |
||||
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 = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities()); |
||||
SecurityContext context = SecurityContextHolder.createEmptyContext(); |
||||
context.setAuthentication(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 there are many tests related to an administrative user with the username "admin" and the roles `ROLE_USER` and `ROLE_ADMIN` you would have to write: |
||||
|
||||
[source,java] |
||||
---- |
||||
@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`: |
||||
|
||||
[source,java] |
||||
---- |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@WithMockUser(value="rob",roles="ADMIN") |
||||
public @interface 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. |
||||
@ -0,0 +1,376 @@
@@ -0,0 +1,376 @@
|
||||
|
||||
[[test-mockmvc]] |
||||
== Spring MVC Test Integration |
||||
|
||||
Spring Security provides comprehensive integration with http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#spring-mvc-test-framework[Spring MVC Test] |
||||
|
||||
[[test-mockmvc-setup]] |
||||
=== Setting Up MockMvc and Spring Security |
||||
|
||||
In order to use Spring Security with Spring MVC Test it is necessary to add the Spring Security `FilterChainProxy` as a `Filter`. |
||||
It is also necessary to add Spring Security's `TestSecurityContextHolderPostProcessor` to support <<Running as a User in Spring MVC Test with Annotations,Running as a User in Spring MVC Test with Annotations>>. |
||||
This can be done using Spring Security's `SecurityMockMvcConfigurers.springSecurity()`. |
||||
For example: |
||||
|
||||
NOTE: Spring Security's testing support requires spring-test-4.1.3.RELEASE or greater. |
||||
|
||||
[source,java] |
||||
---- |
||||
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*; |
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class) |
||||
@ContextConfiguration |
||||
@WebAppConfiguration |
||||
public class CsrfShowcaseTests { |
||||
|
||||
@Autowired |
||||
private WebApplicationContext context; |
||||
|
||||
private MockMvc mvc; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
mvc = MockMvcBuilders |
||||
.webAppContextSetup(context) |
||||
.apply(springSecurity()) // <1> |
||||
.build(); |
||||
} |
||||
|
||||
... |
||||
---- |
||||
|
||||
<1> `SecurityMockMvcConfigurers.springSecurity()` will perform all of the initial setup we need to integrate Spring Security with Spring MVC Test |
||||
|
||||
[[test-mockmvc-smmrpp]] |
||||
=== SecurityMockMvcRequestPostProcessors |
||||
|
||||
Spring MVC Test provides a convenient interface called a `RequestPostProcessor` that can be used to modify a request. |
||||
Spring Security provides a number of `RequestPostProcessor` implementations that make testing easier. |
||||
In order to use Spring Security's `RequestPostProcessor` implementations ensure the following static import is used: |
||||
|
||||
[source,java] |
||||
---- |
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; |
||||
---- |
||||
|
||||
[[test-mockmvc-csrf]] |
||||
==== Testing with CSRF Protection |
||||
|
||||
When testing any non-safe HTTP methods and using Spring Security's CSRF protection, you must be sure to include a valid CSRF Token in the request. |
||||
To specify a valid CSRF token as a request parameter using the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(post("/").with(csrf())) |
||||
---- |
||||
|
||||
If you like you can include CSRF token in the header instead: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(post("/").with(csrf().asHeader())) |
||||
---- |
||||
|
||||
You can also test providing an invalid CSRF token using the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(post("/").with(csrf().useInvalidToken())) |
||||
---- |
||||
|
||||
[[test-mockmvc-securitycontextholder]] |
||||
==== Running a Test as a User in Spring MVC Test |
||||
|
||||
It is often desirable to run tests as a specific user. |
||||
There are two simple ways of populating the user: |
||||
|
||||
* <<Running as a User in Spring MVC Test with RequestPostProcessor,Running as a User in Spring MVC Test with RequestPostProcessor>> |
||||
* <<Running as a User in Spring MVC Test with Annotations,Running as a User in Spring MVC Test with Annotations>> |
||||
|
||||
[[test-mockmvc-securitycontextholder-rpp]] |
||||
==== Running as a User in Spring MVC Test with RequestPostProcessor |
||||
|
||||
There are a number of options available to associate a user to the current `HttpServletRequest`. |
||||
For example, the following will run as a user (which does not need to exist) with the username "user", the password "password", and the role "ROLE_USER": |
||||
|
||||
[NOTE] |
||||
==== |
||||
The support works by associating the user to the `HttpServletRequest`. |
||||
To associate the request to the `SecurityContextHolder` you need to ensure that the `SecurityContextPersistenceFilter` is associated with the `MockMvc` instance. |
||||
A few ways to do this are: |
||||
|
||||
* Invoking <<test-mockmvc-setup,apply(springSecurity())>> |
||||
* Adding Spring Security's `FilterChainProxy` to `MockMvc` |
||||
* Manually adding `SecurityContextPersistenceFilter` to the `MockMvc` instance may make sense when using `MockMvcBuilders.standaloneSetup` |
||||
==== |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(user("user"))) |
||||
---- |
||||
|
||||
You can easily make customizations. |
||||
For example, the following will run as a user (which does not need to exist) with the username "admin", the password "pass", and the roles "ROLE_USER" and "ROLE_ADMIN". |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/admin").with(user("admin").password("pass").roles("USER","ADMIN"))) |
||||
---- |
||||
|
||||
If you have a custom `UserDetails` that you would like to use, you can easily specify that as well. |
||||
For example, the following will use the specified `UserDetails` (which does not need to exist) to run with a `UsernamePasswordAuthenticationToken` that has a principal of the specified `UserDetails`: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(user(userDetails))) |
||||
---- |
||||
|
||||
You can run as anonymous user using the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(anonymous())) |
||||
---- |
||||
|
||||
This is especially useful if you are running with a default user and wish to execute a few requests as an anonymous user. |
||||
|
||||
If you want a custom `Authentication` (which does not need to exist) you can do so using the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(authentication(authentication))) |
||||
---- |
||||
|
||||
You can even customize the `SecurityContext` using the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(securityContext(securityContext))) |
||||
---- |
||||
|
||||
We can also ensure to run as a specific user for every request by using ``MockMvcBuilders``'s default request. |
||||
For example, the following will run as a user (which does not need to exist) with the username "admin", the password "password", and the role "ROLE_ADMIN": |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc = MockMvcBuilders |
||||
.webAppContextSetup(context) |
||||
.defaultRequest(get("/").with(user("user").roles("ADMIN"))) |
||||
.apply(springSecurity()) |
||||
.build(); |
||||
---- |
||||
|
||||
If you find you are using the same user in many of your tests, it is recommended to move the user to a method. |
||||
For example, you can specify the following in your own class named `CustomSecurityMockMvcRequestPostProcessors`: |
||||
|
||||
[source,java] |
||||
---- |
||||
public static RequestPostProcessor rob() { |
||||
return user("rob").roles("ADMIN"); |
||||
} |
||||
---- |
||||
|
||||
Now you can perform a static import on `SecurityMockMvcRequestPostProcessors` and use that within your tests: |
||||
|
||||
[source,java] |
||||
---- |
||||
import static sample.CustomSecurityMockMvcRequestPostProcessors.*; |
||||
|
||||
... |
||||
|
||||
mvc |
||||
.perform(get("/").with(rob())) |
||||
---- |
||||
|
||||
===== Running as a User in Spring MVC Test with Annotations |
||||
|
||||
As an alternative to using a `RequestPostProcessor` to create your user, you can use annotations described in <<Testing Method Security>>. |
||||
For example, the following will run the test with the user with username "user", password "password", and role "ROLE_USER": |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser |
||||
public void requestProtectedUrlWithUser() throws Exception { |
||||
mvc |
||||
.perform(get("/")) |
||||
... |
||||
} |
||||
---- |
||||
|
||||
Alternatively, the following will run the test with the user with username "user", password "password", and role "ROLE_ADMIN": |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
@WithMockUser(roles="ADMIN") |
||||
public void requestProtectedUrlWithUser() throws Exception { |
||||
mvc |
||||
.perform(get("/")) |
||||
... |
||||
} |
||||
---- |
||||
|
||||
==== Testing HTTP Basic Authentication |
||||
|
||||
While it has always been possible to authenticate with HTTP Basic, it was a bit tedious to remember the header name, format, and encode the values. |
||||
Now this can be done using Spring Security's `httpBasic` `RequestPostProcessor`. |
||||
For example, the snippet below: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(get("/").with(httpBasic("user","password"))) |
||||
---- |
||||
|
||||
will attempt to use HTTP Basic to authenticate a user with the username "user" and the password "password" by ensuring the following header is populated on the HTTP Request: |
||||
|
||||
[source,text] |
||||
---- |
||||
Authorization: Basic dXNlcjpwYXNzd29yZA== |
||||
---- |
||||
|
||||
=== SecurityMockMvcRequestBuilders |
||||
|
||||
Spring MVC Test also provides a `RequestBuilder` interface that can be used to create the `MockHttpServletRequest` used in your test. |
||||
Spring Security provides a few `RequestBuilder` implementations that can be used to make testing easier. |
||||
In order to use Spring Security's `RequestBuilder` implementations ensure the following static import is used: |
||||
|
||||
[source,java] |
||||
---- |
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*; |
||||
---- |
||||
|
||||
==== Testing Form Based Authentication |
||||
|
||||
You can easily create a request to test a form based authentication using Spring Security's testing support. |
||||
For example, the following will submit a POST to "/login" with the username "user", the password "password", and a valid CSRF token: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin()) |
||||
---- |
||||
|
||||
It is easy to customize the request. |
||||
For example, the following will submit a POST to "/auth" with the username "admin", the password "pass", and a valid CSRF token: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin("/auth").user("admin").password("pass")) |
||||
---- |
||||
|
||||
We can also customize the parameters names that the username and password are included on. |
||||
For example, this is the above request modified to include the username on the HTTP parameter "u" and the password on the HTTP parameter "p". |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin("/auth").user("u","admin").password("p","pass")) |
||||
---- |
||||
|
||||
[[test-logout]] |
||||
==== Testing Logout |
||||
|
||||
While fairly trivial using standard Spring MVC Test, you can use Spring Security's testing support to make testing log out easier. |
||||
For example, the following will submit a POST to "/logout" with a valid CSRF token: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(logout()) |
||||
---- |
||||
|
||||
You can also customize the URL to post to. |
||||
For example, the snippet below will submit a POST to "/signout" with a valid CSRF token: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(logout("/signout")) |
||||
---- |
||||
|
||||
=== SecurityMockMvcResultMatchers |
||||
|
||||
At times it is desirable to make various security related assertions about a request. |
||||
To accommodate this need, Spring Security Test support implements Spring MVC Test's `ResultMatcher` interface. |
||||
In order to use Spring Security's `ResultMatcher` implementations ensure the following static import is used: |
||||
|
||||
[source,java] |
||||
---- |
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*; |
||||
---- |
||||
|
||||
==== Unauthenticated Assertion |
||||
|
||||
At times it may be valuable to assert that there is no authenticated user associated with the result of a `MockMvc` invocation. |
||||
For example, you might want to test submitting an invalid username and password and verify that no user is authenticated. |
||||
You can easily do this with Spring Security's testing support using something like the following: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin().password("invalid")) |
||||
.andExpect(unauthenticated()); |
||||
---- |
||||
|
||||
==== Authenticated Assertion |
||||
|
||||
It is often times that we must assert that an authenticated user exists. |
||||
For example, we may want to verify that we authenticated successfully. |
||||
We could verify that a form based login was successful with the following snippet of code: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin()) |
||||
.andExpect(authenticated()); |
||||
---- |
||||
|
||||
If we wanted to assert the roles of the user, we could refine our previous code as shown below: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin().user("admin")) |
||||
.andExpect(authenticated().withRoles("USER","ADMIN")); |
||||
---- |
||||
|
||||
Alternatively, we could verify the username: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin().user("admin")) |
||||
.andExpect(authenticated().withUsername("admin")); |
||||
---- |
||||
|
||||
We can also combine the assertions: |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin().user("admin").roles("USER","ADMIN")) |
||||
.andExpect(authenticated().withUsername("admin")); |
||||
---- |
||||
|
||||
We can also make arbitrary assertions on the authentication |
||||
|
||||
[source,java] |
||||
---- |
||||
mvc |
||||
.perform(formLogin()) |
||||
.andExpect(authenticated().withAuthentication(auth -> |
||||
assertThat(auth).isInstanceOf(UsernamePasswordAuthenticationToken.class))); |
||||
---- |
||||
@ -0,0 +1,150 @@
@@ -0,0 +1,150 @@
|
||||
[[test-webflux]] |
||||
== WebFlux Support |
||||
|
||||
[[test-erms]] |
||||
=== Reactive Method Security |
||||
|
||||
For example, we can test our example from <<jc-erms>> using the same setup and annotations we did in <<test-method>>. |
||||
Here is a minimal sample of what we can do: |
||||
|
||||
[source,java] |
||||
---- |
||||
@RunWith(SpringRunner.class) |
||||
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class) |
||||
public class HelloWorldMessageServiceTests { |
||||
@Autowired |
||||
HelloWorldMessageService messages; |
||||
|
||||
@Test |
||||
public void messagesWhenNotAuthenticatedThenDenied() { |
||||
StepVerifier.create(this.messages.findMessage()) |
||||
.expectError(AccessDeniedException.class) |
||||
.verify(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
public void messagesWhenUserThenDenied() { |
||||
StepVerifier.create(this.messages.findMessage()) |
||||
.expectError(AccessDeniedException.class) |
||||
.verify(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser(roles = "ADMIN") |
||||
public void messagesWhenAdminThenOk() { |
||||
StepVerifier.create(this.messages.findMessage()) |
||||
.expectNext("Hello World!") |
||||
.verifyComplete(); |
||||
} |
||||
} |
||||
---- |
||||
|
||||
[[test-webtestclient]] |
||||
=== WebTestClientSupport |
||||
|
||||
Spring Security provides integration with `WebTestClient`. |
||||
The basic setup looks like this: |
||||
|
||||
[source,java] |
||||
---- |
||||
@RunWith(SpringRunner.class) |
||||
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class) |
||||
public class HelloWebfluxMethodApplicationTests { |
||||
@Autowired |
||||
ApplicationContext context; |
||||
|
||||
WebTestClient rest; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
this.rest = WebTestClient |
||||
.bindToApplicationContext(this.context) |
||||
// add Spring Security test Support |
||||
.apply(springSecurity()) |
||||
.configureClient() |
||||
.filter(basicAuthentication()) |
||||
.build(); |
||||
} |
||||
// ... |
||||
} |
||||
---- |
||||
|
||||
==== Authentication |
||||
|
||||
After applying the Spring Security support to `WebTestClient` we can use either annotations or `mutateWith` support. |
||||
For example: |
||||
|
||||
[source,java] |
||||
---- |
||||
@Test |
||||
public void messageWhenNotAuthenticated() throws Exception { |
||||
this.rest |
||||
.get() |
||||
.uri("/message") |
||||
.exchange() |
||||
.expectStatus().isUnauthorized(); |
||||
} |
||||
|
||||
// --- WithMockUser --- |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
public void messageWhenWithMockUserThenForbidden() throws Exception { |
||||
this.rest |
||||
.get() |
||||
.uri("/message") |
||||
.exchange() |
||||
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser(roles = "ADMIN") |
||||
public void messageWhenWithMockAdminThenOk() throws Exception { |
||||
this.rest |
||||
.get() |
||||
.uri("/message") |
||||
.exchange() |
||||
.expectStatus().isOk() |
||||
.expectBody(String.class).isEqualTo("Hello World!"); |
||||
} |
||||
|
||||
// --- mutateWith mockUser --- |
||||
|
||||
@Test |
||||
public void messageWhenMutateWithMockUserThenForbidden() throws Exception { |
||||
this.rest |
||||
.mutateWith(mockUser()) |
||||
.get() |
||||
.uri("/message") |
||||
.exchange() |
||||
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN); |
||||
} |
||||
|
||||
@Test |
||||
public void messageWhenMutateWithMockAdminThenOk() throws Exception { |
||||
this.rest |
||||
.mutateWith(mockUser().roles("ADMIN")) |
||||
.get() |
||||
.uri("/message") |
||||
.exchange() |
||||
.expectStatus().isOk() |
||||
.expectBody(String.class).isEqualTo("Hello World!"); |
||||
} |
||||
---- |
||||
|
||||
|
||||
==== CSRF Support |
||||
|
||||
Spring Security also provides support for CSRF testing with `WebTestClient`. |
||||
For example: |
||||
|
||||
[source,java] |
||||
---- |
||||
this.rest |
||||
// provide a valid CSRF token |
||||
.mutateWith(csrf()) |
||||
.post() |
||||
.uri("/login") |
||||
... |
||||
---- |
||||
Loading…
Reference in new issue