Skip to content

Commit

Permalink
Separate Testing Servlet Docs
Browse files Browse the repository at this point in the history
Issue gh-10367
  • Loading branch information
jzheaux committed Oct 29, 2021
1 parent f39d272 commit f02a7d2
Show file tree
Hide file tree
Showing 14 changed files with 820 additions and 818 deletions.
13 changes: 12 additions & 1 deletion docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,18 @@
*** xref:servlet/configuration/xml-namespace.adoc[Namespace Configuration]
** xref:servlet/test/index.adoc[Testing]
*** xref:servlet/test/method.adoc[Method Security]
*** xref:servlet/test/mockmvc.adoc[MockMvc Support]
*** xref:servlet/test/mockmvc/index.adoc[MockMvc Support]
*** xref:servlet/test/mockmvc/setup.adoc[MockMvc Setup]
*** xref:servlet/test/mockmvc/request-post-processors.adoc[Security RequestPostProcessors]
**** xref:servlet/test/mockmvc/authentication.adoc[Mocking Users]
**** xref:servlet/test/mockmvc/csrf.adoc[Mocking CSRF]
**** xref:servlet/test/mockmvc/form-login.adoc[Mocking Form Login]
**** xref:servlet/test/mockmvc/http-basic.adoc[Mocking HTTP Basic]
**** xref:servlet/test/mockmvc/oauth2.adoc[Mocking OAuth2]
**** xref:servlet/test/mockmvc/logout.adoc[Mocking Logout]
*** xref:servlet/test/mockmvc/request-builders.adoc[Security RequestBuilders]
*** xref:servlet/test/mockmvc/result-matchers.adoc[Security ResultMatchers]
*** xref:servlet/test/mockmvc/result-handlers.adoc[Security ResultHandlers]
** xref:servlet/appendix/index.adoc[Appendix]
*** xref:servlet/appendix/database-schema.adoc[Database Schemas]
*** xref:servlet/appendix/namespace.adoc[XML Namespace]
Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/servlet/authentication/logout.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ If not configured a status code 200 will be returned by default.
== Further Logout-Related References

- <<ns-logout, Logout Handling>>
- xref:servlet/test/mockmvc.adoc#test-logout[ Testing Logout]
- xref:servlet/test/mockmvc/logout.adoc#test-logout[ Testing Logout]
- xref:servlet/integrations/servlet-api.adoc#servletapi-logout[ HttpServletRequest.logout()]
- xref:servlet/authentication/rememberme.adoc#remember-me-impls[Remember-Me Interfaces and Implementations]
- xref:servlet/exploits/csrf.adoc#servlet-considerations-csrf-logout[ Logging Out] in section CSRF Caveats
Expand Down
274 changes: 274 additions & 0 deletions docs/modules/ROOT/pages/servlet/test/mockmvc/authentication.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
[[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 xref:servlet/test/mockmvc/setup.adoc#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`
====

====
.Java
[source,java,role="primary"]
----
mvc
.perform(get("/").with(user("user")))
----
.Kotlin
[source,kotlin,role="secondary"]
----
mvc.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".

====
.Java
[source,java,role="primary"]
----
mvc
.perform(get("/admin").with(user("admin").password("pass").roles("USER","ADMIN")))
----
.Kotlin
[source,kotlin,role="secondary"]
----
mvc.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`:

====
.Java
[source,java,role="primary"]
----
mvc
.perform(get("/").with(user(userDetails)))
----
.Kotlin
[source,kotlin,role="secondary"]
----
mvc.get("/") {
with(user(userDetails))
}
----
====

You can run as anonymous user using the following:

====
.Java
[source,java,role="primary"]
----
mvc
.perform(get("/").with(anonymous()))
----
.Kotlin
[source,kotlin,role="secondary"]
----
mvc.get("/") {
with(anonymous())
}
----
====

This is especially useful if you are running with a default user and wish to process 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:

====
.Java
[source,java,role="primary"]
----
mvc
.perform(get("/").with(authentication(authentication)))
----
.Kotlin
[source,kotlin,role="secondary"]
----
mvc.get("/") {
with(authentication(authentication))
}
----
====

You can even customize the `SecurityContext` using the following:

====
.Java
[source,java,role="primary"]
----
mvc
.perform(get("/").with(securityContext(securityContext)))
----
.Kotlin
[source,kotlin,role="secondary"]
----
mvc.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":

====
.Java
[source,java,role="primary"]
----
mvc = MockMvcBuilders
.webAppContextSetup(context)
.defaultRequest(get("/").with(user("user").roles("ADMIN")))
.apply(springSecurity())
.build();
----
.Kotlin
[source,kotlin,role="secondary"]
----
mvc = MockMvcBuilders
.webAppContextSetup(context)
.defaultRequest<DefaultMockMvcBuilder>(get("/").with(user("user").roles("ADMIN")))
.apply<DefaultMockMvcBuilder>(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`:

====
.Java
[source,java,role="primary"]
----
public static RequestPostProcessor rob() {
return user("rob").roles("ADMIN");
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
fun rob(): RequestPostProcessor {
return user("rob").roles("ADMIN")
}
----
====

Now you can perform a static import on `CustomSecurityMockMvcRequestPostProcessors` and use that within your tests:

====
.Java
[source,java,role="primary"]
----
import static sample.CustomSecurityMockMvcRequestPostProcessors.*;
...
mvc
.perform(get("/").with(rob()))
----
.Kotlin
[source,kotlin,role="secondary"]
----
import sample.CustomSecurityMockMvcRequestPostProcessors.*
//...
mvc.get("/") {
with(rob())
}
----
====

[[test-mockmvc-withmockuser]]
== 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 xref:servlet/test/method.adoc[Testing Method Security].
For example, the following will run the test with the user with username "user", password "password", and role "ROLE_USER":

====
.Java
[source,java,role="primary"]
----
@Test
@WithMockUser
public void requestProtectedUrlWithUser() throws Exception {
mvc
.perform(get("/"))
...
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test
@WithMockUser
fun requestProtectedUrlWithUser() {
mvc
.get("/")
// ...
}
----
====

Alternatively, the following will run the test with the user with username "user", password "password", and role "ROLE_ADMIN":

====
.Java
[source,java,role="primary"]
----
@Test
@WithMockUser(roles="ADMIN")
public void requestProtectedUrlWithUser() throws Exception {
mvc
.perform(get("/"))
...
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test
@WithMockUser(roles = ["ADMIN"])
fun requestProtectedUrlWithUser() {
mvc
.get("/")
// ...
}
----
====
60 changes: 60 additions & 0 deletions docs/modules/ROOT/pages/servlet/test/mockmvc/csrf.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
[[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 use the CSRF xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`] like so:

====
.Java
[source,java,role="primary"]
----
mvc
.perform(post("/").with(csrf()))
----
.Kotlin
[source,kotlin,role="secondary"]
----
mvc.post("/") {
with(csrf())
}
----
====

If you like you can include CSRF token in the header instead:

====
.Java
[source,java,role="primary"]
----
mvc
.perform(post("/").with(csrf().asHeader()))
----
.Kotlin
[source,kotlin,role="secondary"]
----
mvc.post("/") {
with(csrf().asHeader())
}
----
====

You can also test providing an invalid CSRF token using the following:

====
.Java
[source,java,role="primary"]
----
mvc
.perform(post("/").with(csrf().useInvalidToken()))
----
.Kotlin
[source,kotlin,role="secondary"]
----
mvc.post("/") {
with(csrf().useInvalidToken())
}
----
====
Loading

0 comments on commit f02a7d2

Please sign in to comment.