123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- [[test]]
- = Testing
- [[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 `@SecurityExecutionListeners`.
- 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 ran 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 {
- ----
- [[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();
- ...
- }
- ----
- 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.
- [[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]
- ----
- @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 `WithSecurityContextTestExcecutionListener` 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;
- }
- }
- ----
- [[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 populate a test user.
- 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":
- [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)))
- ----
- 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("a","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"));
- ----
|