From ae9075c0238da511e97af0f8676726411e88b918 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 6 Mar 2018 09:24:13 -0600 Subject: [PATCH] Extract test subsections Issue: gh-2567 --- .../src/docs/asciidoc/_includes/test.adoc | 898 ------------------ .../docs/asciidoc/_includes/test/index.adoc | 15 + .../docs/asciidoc/_includes/test/method.adoc | 356 +++++++ .../docs/asciidoc/_includes/test/mockmvc.adoc | 376 ++++++++ .../_includes/test/webtestclient.adoc | 150 +++ docs/manual/src/docs/asciidoc/index.adoc | 2 +- 6 files changed, 898 insertions(+), 899 deletions(-) delete mode 100644 docs/manual/src/docs/asciidoc/_includes/test.adoc create mode 100644 docs/manual/src/docs/asciidoc/_includes/test/index.adoc create mode 100644 docs/manual/src/docs/asciidoc/_includes/test/method.adoc create mode 100644 docs/manual/src/docs/asciidoc/_includes/test/mockmvc.adoc create mode 100644 docs/manual/src/docs/asciidoc/_includes/test/webtestclient.adoc diff --git a/docs/manual/src/docs/asciidoc/_includes/test.adoc b/docs/manual/src/docs/asciidoc/_includes/test.adoc deleted file mode 100644 index 59bfdbeb50..0000000000 --- a/docs/manual/src/docs/asciidoc/_includes/test.adoc +++ /dev/null @@ -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 <> 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 { - @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 { - - 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 <>. -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: - -* <> -* <> - -[[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 <> -* 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 <>. -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 <> using the same setup and annotations we did in <>. -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") - ... ----- diff --git a/docs/manual/src/docs/asciidoc/_includes/test/index.adoc b/docs/manual/src/docs/asciidoc/_includes/test/index.adoc new file mode 100644 index 0000000000..c640f0199a --- /dev/null +++ b/docs/manual/src/docs/asciidoc/_includes/test/index.adoc @@ -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[] diff --git a/docs/manual/src/docs/asciidoc/_includes/test/method.adoc b/docs/manual/src/docs/asciidoc/_includes/test/method.adoc new file mode 100644 index 0000000000..a2cc64e51d --- /dev/null +++ b/docs/manual/src/docs/asciidoc/_includes/test/method.adoc @@ -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 <> 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 { + @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 { + + 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. diff --git a/docs/manual/src/docs/asciidoc/_includes/test/mockmvc.adoc b/docs/manual/src/docs/asciidoc/_includes/test/mockmvc.adoc new file mode 100644 index 0000000000..592b82dd81 --- /dev/null +++ b/docs/manual/src/docs/asciidoc/_includes/test/mockmvc.adoc @@ -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 <>. +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: + +* <> +* <> + +[[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 <> +* 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 <>. +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))); +---- diff --git a/docs/manual/src/docs/asciidoc/_includes/test/webtestclient.adoc b/docs/manual/src/docs/asciidoc/_includes/test/webtestclient.adoc new file mode 100644 index 0000000000..36e319671a --- /dev/null +++ b/docs/manual/src/docs/asciidoc/_includes/test/webtestclient.adoc @@ -0,0 +1,150 @@ +[[test-webflux]] +== WebFlux Support + +[[test-erms]] +=== Reactive Method Security + +For example, we can test our example from <> using the same setup and annotations we did in <>. +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") + ... +---- diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index b463e34ca6..b08634a454 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -10,7 +10,7 @@ include::{include-dir}/preface/index.adoc[] include::{include-dir}/architecture/index.adoc[] -include::{include-dir}/test.adoc[] +include::{include-dir}/test/index.adoc[] include::{include-dir}/web.adoc[]