parent
f39d272a86
commit
f02a7d2b28
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("/")
|
||||
// ...
|
||||
}
|
||||
----
|
||||
====
|
|
@ -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())
|
||||
}
|
||||
----
|
||||
====
|
|
@ -0,0 +1,58 @@
|
|||
= 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 `formLogin` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`] will submit a POST to "/login" with the username "user", the password "password", and a valid CSRF token:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin("/auth").user("admin").password("pass"))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
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".
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin("/auth").user("u","admin").password("p","pass"))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin("/auth").user("u","admin").password("p","pass"))
|
||||
----
|
||||
====
|
|
@ -0,0 +1,29 @@
|
|||
= 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` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`].
|
||||
For example, the snippet below:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(get("/").with(httpBasic("user","password")))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc.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==
|
||||
----
|
|
@ -0,0 +1,5 @@
|
|||
[[test-mockmvc]]
|
||||
= Spring MVC Test Integration
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring Security provides comprehensive integration with https://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#spring-mvc-test-framework[Spring MVC Test]
|
|
@ -0,0 +1,40 @@
|
|||
[[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 `logout` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`] will submit a POST to "/logout" with a valid CSRF token:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(logout())
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(logout("/signout"))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(logout("/signout"))
|
||||
----
|
||||
====
|
|
@ -1,460 +1,5 @@
|
|||
[[test-mockmvc]]
|
||||
= Spring MVC Test Integration
|
||||
|
||||
Spring Security provides comprehensive integration with https://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.
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = SecurityConfig.class)
|
||||
@WebAppConfiguration
|
||||
public class CsrfShowcaseTests {
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mvc = MockMvcBuilders
|
||||
.webAppContextSetup(context)
|
||||
.apply(springSecurity()) // <1>
|
||||
.build();
|
||||
}
|
||||
|
||||
...
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@RunWith(SpringJUnit4ClassRunner::class)
|
||||
@ContextConfiguration(classes = [SecurityConfig::class])
|
||||
@WebAppConfiguration
|
||||
class CsrfShowcaseTests {
|
||||
|
||||
@Autowired
|
||||
private lateinit var context: WebApplicationContext
|
||||
|
||||
private var mvc: MockMvc? = null
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mvc = MockMvcBuilders
|
||||
.webAppContextSetup(context)
|
||||
.apply<DefaultMockMvcBuilder>(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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import 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:
|
||||
|
||||
====
|
||||
.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())
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[test-mockmvc-securitycontextholder]]
|
||||
=== Running a Test as a User in Spring MVC Test
|
||||
|
||||
It is often desirable to run tests as a specific user.
|
||||
There are two simple ways of populating the user:
|
||||
|
||||
* <<Running as a User in Spring MVC Test with RequestPostProcessor,Running as a User in Spring MVC Test with RequestPostProcessor>>
|
||||
* <<Running as a User in Spring MVC Test with Annotations,Running as a User in Spring MVC Test with Annotations>>
|
||||
|
||||
[[test-mockmvc-securitycontextholder-rpp]]
|
||||
=== Running as a User in Spring MVC Test with RequestPostProcessor
|
||||
|
||||
There are a number of options available to associate a user to the current `HttpServletRequest`.
|
||||
For example, the following will run as a user (which does not need to exist) with the username "user", the password "password", and the role "ROLE_USER":
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The support works by associating the user to the `HttpServletRequest`.
|
||||
To associate the request to the `SecurityContextHolder` you need to ensure that the `SecurityContextPersistenceFilter` is associated with the `MockMvc` instance.
|
||||
A few ways to do this are:
|
||||
|
||||
* Invoking <<test-mockmvc-setup,apply(springSecurity())>>
|
||||
* Adding Spring Security's `FilterChainProxy` to `MockMvc`
|
||||
* Manually adding `SecurityContextPersistenceFilter` to the `MockMvc` instance may make sense when using `MockMvcBuilders.standaloneSetup`
|
||||
====
|
||||
|
||||
====
|
||||
.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 `SecurityMockMvcRequestPostProcessors` 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())
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
==== 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("/")
|
||||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
=== 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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(get("/").with(httpBasic("user","password")))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc.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==
|
||||
----
|
||||
|
||||
[[testing-oauth2]]
|
||||
=== Testing OAuth 2.0
|
||||
= Testing OAuth 2.0
|
||||
|
||||
When it comes to OAuth 2.0, the same principles covered earlier still apply: Ultimately, it depends on what your method under test is expecting to be in the `SecurityContextHolder`.
|
||||
|
||||
|
@ -507,12 +52,12 @@ fun foo(@AuthenticationPrincipal user: OidcUser): String {
|
|||
then Spring Security's test support can come in handy.
|
||||
|
||||
[[testing-oidc-login]]
|
||||
=== Testing OIDC Login
|
||||
== Testing OIDC Login
|
||||
|
||||
Testing the method above with Spring MVC Test would require simulating some kind of grant flow with an authorization server.
|
||||
Certainly this would be a daunting task, which is why Spring Security ships with support for removing this boilerplate.
|
||||
|
||||
For example, we can tell Spring Security to include a default `OidcUser` using the `SecurityMockMvcRequestPostProcessors#oidcLogin` method, like so:
|
||||
For example, we can tell Spring Security to include a default `OidcUser` using the `oidcLogin` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`], like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
|
@ -589,7 +134,7 @@ Further, it also links that `OidcUser` to a simple instance of `OAuth2Authorized
|
|||
This can be handy if your tests <<testing-oauth2-client,use the `@RegisteredOAuth2AuthorizedClient` annotation>>..
|
||||
|
||||
[[testing-oidc-login-authorities]]
|
||||
==== Configuring Authorities
|
||||
== Configuring Authorities
|
||||
|
||||
In many circumstances, your method is protected by filter or method security and needs your `Authentication` to have certain granted authorities to allow the request.
|
||||
|
||||
|
@ -619,7 +164,7 @@ mvc.get("/endpoint") {
|
|||
====
|
||||
|
||||
[[testing-oidc-login-claims]]
|
||||
==== Configuring Claims
|
||||
== Configuring Claims
|
||||
|
||||
And while granted authorities are quite common across all of Spring Security, we also have claims in the case of OAuth 2.0.
|
||||
|
||||
|
@ -678,7 +223,7 @@ mvc.get("/endpoint") {
|
|||
since `OidcUser` collects its claims from `OidcIdToken`.
|
||||
|
||||
[[testing-oidc-login-user]]
|
||||
==== Additional Configurations
|
||||
== Additional Configurations
|
||||
|
||||
There are additional methods, too, for further configuring the authentication; it simply depends on what data your controller expects:
|
||||
|
||||
|
@ -724,7 +269,7 @@ mvc.get("/endpoint") {
|
|||
====
|
||||
|
||||
[[testing-oauth2-login]]
|
||||
=== Testing OAuth 2.0 Login
|
||||
== Testing OAuth 2.0 Login
|
||||
|
||||
As with <<testing-oidc-login,testing OIDC login>>, testing OAuth 2.0 Login presents a similar challenge of mocking a grant flow.
|
||||
And because of that, Spring Security also has test support for non-OIDC use cases.
|
||||
|
@ -751,7 +296,7 @@ fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): String? {
|
|||
----
|
||||
====
|
||||
|
||||
In that case, we can tell Spring Security to include a default `OAuth2User` using the `SecurityMockMvcRequestPostProcessors#oauth2User` method, like so:
|
||||
In that case, we can tell Spring Security to include a default `OAuth2User` using the `oauth2User` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`], like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
|
@ -812,7 +357,7 @@ Further, it also links that `OAuth2User` to a simple instance of `OAuth2Authoriz
|
|||
This can be handy if your tests <<testing-oauth2-client,use the `@RegisteredOAuth2AuthorizedClient` annotation>>.
|
||||
|
||||
[[testing-oauth2-login-authorities]]
|
||||
==== Configuring Authorities
|
||||
== Configuring Authorities
|
||||
|
||||
In many circumstances, your method is protected by filter or method security and needs your `Authentication` to have certain granted authorities to allow the request.
|
||||
|
||||
|
@ -842,7 +387,7 @@ mvc.get("/endpoint") {
|
|||
====
|
||||
|
||||
[[testing-oauth2-login-claims]]
|
||||
==== Configuring Claims
|
||||
== Configuring Claims
|
||||
|
||||
And while granted authorities are quite common across all of Spring Security, we also have claims in the case of OAuth 2.0.
|
||||
|
||||
|
@ -897,7 +442,7 @@ mvc.get("/endpoint") {
|
|||
====
|
||||
|
||||
[[testing-oauth2-login-user]]
|
||||
==== Additional Configurations
|
||||
== Additional Configurations
|
||||
|
||||
There are additional methods, too, for further configuring the authentication; it simply depends on what data your controller expects:
|
||||
|
||||
|
@ -942,7 +487,7 @@ mvc.get("/endpoint") {
|
|||
====
|
||||
|
||||
[[testing-oauth2-client]]
|
||||
=== Testing OAuth 2.0 Clients
|
||||
== Testing OAuth 2.0 Clients
|
||||
|
||||
Independent of how your user authenticates, you may have other tokens and client registrations that are in play for the request you are testing.
|
||||
For example, your controller may be relying on the client credentials grant to get a token that isn't associated with the user at all:
|
||||
|
@ -976,7 +521,7 @@ fun foo(@RegisteredOAuth2AuthorizedClient("my-app") authorizedClient: OAuth2Auth
|
|||
====
|
||||
|
||||
Simulating this handshake with the authorization server could be cumbersome.
|
||||
Instead, you can use `SecurityMockMvcRequestPostProcessor#oauth2Client` to add a `OAuth2AuthorizedClient` into a mock `OAuth2AuthorizedClientRepository`:
|
||||
Instead, you can use the `oauth2Client` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`] to add a `OAuth2AuthorizedClient` into a mock `OAuth2AuthorizedClientRepository`:
|
||||
|
||||
====
|
||||
.Java
|
||||
|
@ -1054,7 +599,7 @@ assertThat(authorizedClient.accessToken.scopes).containsExactly("read")
|
|||
The client can then be retrieved as normal using `@RegisteredOAuth2AuthorizedClient` in a controller method.
|
||||
|
||||
[[testing-oauth2-client-scopes]]
|
||||
==== Configuring Scopes
|
||||
== Configuring Scopes
|
||||
|
||||
In many circumstances, the OAuth 2.0 access token comes with a set of scopes.
|
||||
If your controller inspects these, say like so:
|
||||
|
@ -1121,7 +666,7 @@ mvc.get("/endpoint") {
|
|||
====
|
||||
|
||||
[[testing-oauth2-client-registration]]
|
||||
==== Additional Configurations
|
||||
== Additional Configurations
|
||||
|
||||
There are additional methods, too, for further configuring the authentication; it simply depends on what data your controller expects:
|
||||
|
||||
|
@ -1167,7 +712,7 @@ mvc.get("/endpoint") {
|
|||
====
|
||||
|
||||
[[testing-jwt]]
|
||||
=== Testing JWT Authentication
|
||||
== Testing JWT Authentication
|
||||
|
||||
In order to make an authorized request on a resource server, you need a bearer token.
|
||||
|
||||
|
@ -1177,9 +722,9 @@ All of this can be quite daunting, especially when this isn't the focus of your
|
|||
Fortunately, there are a number of simple ways that you can overcome this difficulty and allow your tests to focus on authorization and not on representing bearer tokens.
|
||||
We'll look at two of them now:
|
||||
|
||||
==== `jwt() RequestPostProcessor`
|
||||
== `jwt() RequestPostProcessor`
|
||||
|
||||
The first way is via a `RequestPostProcessor`.
|
||||
The first way is via the `jwt` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`].
|
||||
The simplest of these would look something like this:
|
||||
|
||||
====
|
||||
|
@ -1357,9 +902,9 @@ mvc.get("/endpoint") {
|
|||
----
|
||||
====
|
||||
|
||||
==== `authentication()` `RequestPostProcessor`
|
||||
== `authentication()` `RequestPostProcessor`
|
||||
|
||||
The second way is by using the `authentication()` `RequestPostProcessor`.
|
||||
The second way is by using the `authentication()` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`].
|
||||
Essentially, you can instantiate your own `JwtAuthenticationToken` and provide it in your test, like so:
|
||||
|
||||
====
|
||||
|
@ -1399,7 +944,7 @@ mvc.get("/endpoint") {
|
|||
Note that as an alternative to these, you can also mock the `JwtDecoder` bean itself with a `@MockBean` annotation.
|
||||
|
||||
[[testing-opaque-token]]
|
||||
=== Testing Opaque Token Authentication
|
||||
== Testing Opaque Token Authentication
|
||||
|
||||
Similar to <<testing-jwt,JWTs>>, opaque tokens require an authorization server in order to verify their validity, which can make testing more difficult.
|
||||
To help with that, Spring Security has test support for opaque tokens.
|
||||
|
@ -1426,7 +971,7 @@ fun foo(authentication: BearerTokenAuthentication): String {
|
|||
----
|
||||
====
|
||||
|
||||
In that case, we can tell Spring Security to include a default `BearerTokenAuthentication` using the `SecurityMockMvcRequestPostProcessors#opaqueToken` method, like so:
|
||||
In that case, we can tell Spring Security to include a default `BearerTokenAuthentication` using the `opaqueToken` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`] method, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
|
@ -1484,7 +1029,7 @@ assertThat(token.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read
|
|||
Spring Security does the necessary work to make sure that the `BearerTokenAuthentication` instance is available for your controller methods.
|
||||
|
||||
[[testing-opaque-token-authorities]]
|
||||
==== Configuring Authorities
|
||||
== Configuring Authorities
|
||||
|
||||
In many circumstances, your method is protected by filter or method security and needs your `Authentication` to have certain granted authorities to allow the request.
|
||||
|
||||
|
@ -1514,7 +1059,7 @@ mvc.get("/endpoint") {
|
|||
====
|
||||
|
||||
[[testing-opaque-token-attributes]]
|
||||
==== Configuring Claims
|
||||
== Configuring Claims
|
||||
|
||||
And while granted authorities are quite common across all of Spring Security, we also have attributes in the case of OAuth 2.0.
|
||||
|
||||
|
@ -1569,7 +1114,7 @@ mvc.get("/endpoint") {
|
|||
====
|
||||
|
||||
[[testing-opaque-token-principal]]
|
||||
==== Additional Configurations
|
||||
== Additional Configurations
|
||||
|
||||
There are additional methods, too, for further configuring the authentication; it simply depends on what data your controller expects.
|
||||
|
||||
|
@ -1615,339 +1160,3 @@ mvc.get("/endpoint") {
|
|||
====
|
||||
|
||||
Note that as an alternative to using `opaqueToken()` test support, you can also mock the `OpaqueTokenIntrospector` bean itself with a `@MockBean` annotation.
|
||||
|
||||
== 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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import 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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin("/auth").user("admin").password("pass"))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
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".
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin("/auth").user("u","admin").password("p","pass"))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(logout())
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(logout("/signout"))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*;
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import 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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().password("invalid"))
|
||||
.andExpect(unauthenticated());
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
.andExpect(authenticated());
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
.andExpect { authenticated() }
|
||||
----
|
||||
====
|
||||
|
||||
If we wanted to assert the roles of the user, we could refine our previous code as shown below:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().user("admin"))
|
||||
.andExpect(authenticated().withRoles("USER","ADMIN"));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
.andExpect { authenticated().withRoles("USER","ADMIN") }
|
||||
----
|
||||
====
|
||||
|
||||
Alternatively, we could verify the username:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().user("admin"))
|
||||
.andExpect(authenticated().withUsername("admin"));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().user("admin"))
|
||||
.andExpect { authenticated().withUsername("admin") }
|
||||
----
|
||||
====
|
||||
|
||||
We can also combine the assertions:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().user("admin"))
|
||||
.andExpect(authenticated().withUsername("admin").withRoles("USER", "ADMIN"));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().user("admin"))
|
||||
.andExpect { authenticated().withUsername("admin").withRoles("USER", "ADMIN") }
|
||||
----
|
||||
====
|
||||
|
||||
We can also make arbitrary assertions on the authentication
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
.andExpect(authenticated().withAuthentication(auth ->
|
||||
assertThat(auth).isInstanceOf(UsernamePasswordAuthenticationToken.class)));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
.andExpect {
|
||||
authenticated().withAuthentication { auth ->
|
||||
assertThat(auth).isInstanceOf(UsernamePasswordAuthenticationToken::class.java) }
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
=== SecurityMockMvcResultHandlers
|
||||
|
||||
Spring Security provides a few ``ResultHandler``s implementations.
|
||||
In order to use Spring Security's ``ResultHandler``s implementations ensure the following static import is used:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultHandlers.*;
|
||||
----
|
||||
|
||||
==== Exporting the SecurityContext
|
||||
|
||||
Often times we want to query a repository to see if some `MockMvc` request actually persisted in the database.
|
||||
In some cases our repository query uses the xref:features/integrations/data.adoc[Spring Data Integration] to filter the results based on current user's username or any other property.
|
||||
Let's see an example:
|
||||
|
||||
A repository interface:
|
||||
[source,java]
|
||||
----
|
||||
private interface MessageRepository extends JpaRepository<Message, Long> {
|
||||
@Query("SELECT m.content FROM Message m WHERE m.sentBy = ?#{ principal?.name }")
|
||||
List<String> findAllUserMessages();
|
||||
}
|
||||
----
|
||||
|
||||
Our test scenario:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
mvc
|
||||
.perform(post("/message")
|
||||
.content("New Message")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
)
|
||||
.andExpect(status().isOk());
|
||||
|
||||
List<String> userMessages = messageRepository.findAllUserMessages();
|
||||
assertThat(userMessages).hasSize(1);
|
||||
----
|
||||
|
||||
This test won't pass because after our request finishes, the `SecurityContextHolder` will be cleared out by the filter chain.
|
||||
We can then export the `TestSecurityContextHolder` to our `SecurityContextHolder` and use it as we want:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
mvc
|
||||
.perform(post("/message")
|
||||
.content("New Message")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
)
|
||||
.andDo(exportTestSecurityContext())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
List<String> userMessages = messageRepository.findAllUserMessages();
|
||||
assertThat(userMessages).hasSize(1);
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Remember to clear the `SecurityContextHolder` between your tests, or it may leak amongst them
|
||||
====
|
|
@ -0,0 +1,19 @@
|
|||
== 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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*
|
||||
----
|
||||
====
|
|
@ -0,0 +1,20 @@
|
|||
[[test-mockmvc-smmrpp]]
|
||||
= SecurityMockMvcRequestPostProcessors
|
||||
:page-section-summary-toc: 1
|
||||
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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*
|
||||
----
|
||||
====
|
|
@ -0,0 +1,61 @@
|
|||
=== SecurityMockMvcResultHandlers
|
||||
|
||||
Spring Security provides a few ``ResultHandler``s implementations.
|
||||
In order to use Spring Security's ``ResultHandler``s implementations ensure the following static import is used:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultHandlers.*;
|
||||
----
|
||||
|
||||
==== Exporting the SecurityContext
|
||||
|
||||
Often times we want to query a repository to see if some `MockMvc` request actually persisted in the database.
|
||||
In some cases our repository query uses the xref:features/integrations/data.adoc[Spring Data Integration] to filter the results based on current user's username or any other property.
|
||||
Let's see an example:
|
||||
|
||||
A repository interface:
|
||||
[source,java]
|
||||
----
|
||||
private interface MessageRepository extends JpaRepository<Message, Long> {
|
||||
@Query("SELECT m.content FROM Message m WHERE m.sentBy = ?#{ principal?.name }")
|
||||
List<String> findAllUserMessages();
|
||||
}
|
||||
----
|
||||
|
||||
Our test scenario:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
mvc
|
||||
.perform(post("/message")
|
||||
.content("New Message")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
)
|
||||
.andExpect(status().isOk());
|
||||
|
||||
List<String> userMessages = messageRepository.findAllUserMessages();
|
||||
assertThat(userMessages).hasSize(1);
|
||||
----
|
||||
|
||||
This test won't pass because after our request finishes, the `SecurityContextHolder` will be cleared out by the filter chain.
|
||||
We can then export the `TestSecurityContextHolder` to our `SecurityContextHolder` and use it as we want:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
mvc
|
||||
.perform(post("/message")
|
||||
.content("New Message")
|
||||
.contentType(MediaType.TEXT_PLAIN)
|
||||
)
|
||||
.andDo(exportTestSecurityContext())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
List<String> userMessages = messageRepository.findAllUserMessages();
|
||||
assertThat(userMessages).hasSize(1);
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Remember to clear the `SecurityContextHolder` between your tests, or it may leak amongst them
|
||||
====
|
|
@ -0,0 +1,153 @@
|
|||
== 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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*;
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import 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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().password("invalid"))
|
||||
.andExpect(unauthenticated());
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
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:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
.andExpect(authenticated());
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
.andExpect { authenticated() }
|
||||
----
|
||||
====
|
||||
|
||||
If we wanted to assert the roles of the user, we could refine our previous code as shown below:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().user("admin"))
|
||||
.andExpect(authenticated().withRoles("USER","ADMIN"));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
.andExpect { authenticated().withRoles("USER","ADMIN") }
|
||||
----
|
||||
====
|
||||
|
||||
Alternatively, we could verify the username:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().user("admin"))
|
||||
.andExpect(authenticated().withUsername("admin"));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().user("admin"))
|
||||
.andExpect { authenticated().withUsername("admin") }
|
||||
----
|
||||
====
|
||||
|
||||
We can also combine the assertions:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().user("admin"))
|
||||
.andExpect(authenticated().withUsername("admin").withRoles("USER", "ADMIN"));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin().user("admin"))
|
||||
.andExpect { authenticated().withUsername("admin").withRoles("USER", "ADMIN") }
|
||||
----
|
||||
====
|
||||
|
||||
We can also make arbitrary assertions on the authentication
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
.andExpect(authenticated().withAuthentication(auth ->
|
||||
assertThat(auth).isInstanceOf(UsernamePasswordAuthenticationToken.class)));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
mvc
|
||||
.perform(formLogin())
|
||||
.andExpect {
|
||||
authenticated().withAuthentication { auth ->
|
||||
assertThat(auth).isInstanceOf(UsernamePasswordAuthenticationToken::class.java) }
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
|
@ -0,0 +1,63 @@
|
|||
[[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 xref:servlet/test/mockmvc/setup.adoc#test-mockmvc-withmockuser[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.
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = SecurityConfig.class)
|
||||
@WebAppConfiguration
|
||||
public class CsrfShowcaseTests {
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mvc = MockMvcBuilders
|
||||
.webAppContextSetup(context)
|
||||
.apply(springSecurity()) // <1>
|
||||
.build();
|
||||
}
|
||||
|
||||
...
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@RunWith(SpringJUnit4ClassRunner::class)
|
||||
@ContextConfiguration(classes = [SecurityConfig::class])
|
||||
@WebAppConfiguration
|
||||
class CsrfShowcaseTests {
|
||||
|
||||
@Autowired
|
||||
private lateinit var context: WebApplicationContext
|
||||
|
||||
private var mvc: MockMvc? = null
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mvc = MockMvcBuilders
|
||||
.webAppContextSetup(context)
|
||||
.apply<DefaultMockMvcBuilder>(springSecurity()) // <1>
|
||||
.build()
|
||||
}
|
||||
// ...
|
||||
----
|
||||
====
|
||||
|
||||
<1> `SecurityMockMvcConfigurers.springSecurity()` will perform all of the initial setup we need to integrate Spring Security with Spring MVC Test
|
Loading…
Reference in New Issue