|
@@ -1,259 +1,7 @@
|
|
|
-[[test-webflux]]
|
|
|
-= Reactive Test Support
|
|
|
-
|
|
|
-[[test-erms]]
|
|
|
-== Testing Reactive Method Security
|
|
|
-
|
|
|
-For example, we can test our example from xref:reactive/authorization/method.adoc#jc-erms[EnableReactiveMethodSecurity] using the same setup and annotations we did in xref:servlet/test/method.adoc#test-method[Testing Method Security].
|
|
|
-Here is a minimal sample of what we can do:
|
|
|
-
|
|
|
-====
|
|
|
-.Java
|
|
|
-[source,java,role="primary"]
|
|
|
-----
|
|
|
-@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();
|
|
|
- }
|
|
|
-}
|
|
|
-----
|
|
|
-
|
|
|
-.Kotlin
|
|
|
-[source,kotlin,role="secondary"]
|
|
|
-----
|
|
|
-@RunWith(SpringRunner::class)
|
|
|
-@ContextConfiguration(classes = [HelloWebfluxMethodApplication::class])
|
|
|
-class HelloWorldMessageServiceTests {
|
|
|
- @Autowired
|
|
|
- lateinit var messages: HelloWorldMessageService
|
|
|
-
|
|
|
- @Test
|
|
|
- fun messagesWhenNotAuthenticatedThenDenied() {
|
|
|
- StepVerifier.create(messages.findMessage())
|
|
|
- .expectError(AccessDeniedException::class.java)
|
|
|
- .verify()
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- @WithMockUser
|
|
|
- fun messagesWhenUserThenDenied() {
|
|
|
- StepVerifier.create(messages.findMessage())
|
|
|
- .expectError(AccessDeniedException::class.java)
|
|
|
- .verify()
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- @WithMockUser(roles = ["ADMIN"])
|
|
|
- fun messagesWhenAdminThenOk() {
|
|
|
- StepVerifier.create(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:
|
|
|
-
|
|
|
-====
|
|
|
-.Java
|
|
|
-[source,java,role="primary"]
|
|
|
-----
|
|
|
-@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!");
|
|
|
-}
|
|
|
-----
|
|
|
-
|
|
|
-.Kotlin
|
|
|
-[source,kotlin,role="secondary"]
|
|
|
-----
|
|
|
-import org.springframework.test.web.reactive.server.expectBody
|
|
|
-
|
|
|
-//...
|
|
|
-
|
|
|
-@Test
|
|
|
-@WithMockUser
|
|
|
-fun messageWhenWithMockUserThenForbidden() {
|
|
|
- this.rest.get().uri("/message")
|
|
|
- .exchange()
|
|
|
- .expectStatus().isEqualTo(HttpStatus.FORBIDDEN)
|
|
|
-}
|
|
|
-
|
|
|
-@Test
|
|
|
-@WithMockUser(roles = ["ADMIN"])
|
|
|
-fun messageWhenWithMockAdminThenOk() {
|
|
|
- this.rest.get().uri("/message")
|
|
|
- .exchange()
|
|
|
- .expectStatus().isOk
|
|
|
- .expectBody<String>().isEqualTo("Hello World!")
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-// --- mutateWith mockUser ---
|
|
|
-
|
|
|
-@Test
|
|
|
-fun messageWhenMutateWithMockUserThenForbidden() {
|
|
|
- this.rest
|
|
|
- .mutateWith(mockUser())
|
|
|
- .get().uri("/message")
|
|
|
- .exchange()
|
|
|
- .expectStatus().isEqualTo(HttpStatus.FORBIDDEN)
|
|
|
-}
|
|
|
-
|
|
|
-@Test
|
|
|
-fun messageWhenMutateWithMockAdminThenOk() {
|
|
|
- this.rest
|
|
|
- .mutateWith(mockUser().roles("ADMIN"))
|
|
|
- .get().uri("/message")
|
|
|
- .exchange()
|
|
|
- .expectStatus().isOk
|
|
|
- .expectBody<String>().isEqualTo("Hello World!")
|
|
|
-}
|
|
|
-----
|
|
|
-====
|
|
|
-
|
|
|
-
|
|
|
-=== CSRF Support
|
|
|
-
|
|
|
-Spring Security also provides support for CSRF testing with `WebTestClient`.
|
|
|
-For example:
|
|
|
-
|
|
|
-====
|
|
|
-.Java
|
|
|
-[source,java,role="primary"]
|
|
|
-----
|
|
|
-this.rest
|
|
|
- // provide a valid CSRF token
|
|
|
- .mutateWith(csrf())
|
|
|
- .post()
|
|
|
- .uri("/login")
|
|
|
- ...
|
|
|
-----
|
|
|
-
|
|
|
-.Kotlin
|
|
|
-[source,kotlin,role="secondary"]
|
|
|
-----
|
|
|
-this.rest
|
|
|
- // provide a valid CSRF token
|
|
|
- .mutateWith(csrf())
|
|
|
- .post()
|
|
|
- .uri("/login")
|
|
|
- ...
|
|
|
-----
|
|
|
-====
|
|
|
-
|
|
|
[[webflux-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`.
|
|
|
+When it comes to OAuth 2.0, xref:reactive/test/method.adoc#test-erms[the same principles covered earlier still apply]: Ultimately, it depends on what your method under test is expecting to be in the `SecurityContextHolder`.
|
|
|
|
|
|
For example, for a controller that looks like this:
|
|
|
|
|
@@ -277,7 +25,7 @@ fun foo(user: Principal): Mono<String> {
|
|
|
----
|
|
|
====
|
|
|
|
|
|
-There's nothing OAuth2-specific about it, so you will likely be able to simply <<test-erms,use `@WithMockUser`>> and be fine.
|
|
|
+There's nothing OAuth2-specific about it, so you will likely be able to simply xref:reactive/test/method.adoc#test-erms[use `@WithMockUser`] and be fine.
|
|
|
|
|
|
But, in cases where your controllers are bound to some aspect of Spring Security's OAuth 2.0 support, like the following:
|
|
|
|
|
@@ -304,7 +52,7 @@ fun foo(@AuthenticationPrincipal user: OidcUser): Mono<String> {
|
|
|
then Spring Security's test support can come in handy.
|
|
|
|
|
|
[[webflux-testing-oidc-login]]
|
|
|
-=== Testing OIDC Login
|
|
|
+== Testing OIDC Login
|
|
|
|
|
|
Testing the method above with `WebTestClient` 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.
|
|
@@ -387,7 +135,7 @@ Further, it also links that `OidcUser` to a simple instance of `OAuth2Authorized
|
|
|
This can be handy if your tests <<webflux-testing-oauth2-client,use the `@RegisteredOAuth2AuthorizedClient` annotation>>..
|
|
|
|
|
|
[[webflux-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.
|
|
|
|
|
@@ -416,7 +164,7 @@ client
|
|
|
====
|
|
|
|
|
|
[[webflux-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.
|
|
|
|
|
@@ -472,7 +220,7 @@ client
|
|
|
since `OidcUser` collects its claims from `OidcIdToken`.
|
|
|
|
|
|
[[webflux-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:
|
|
|
|
|
@@ -517,7 +265,7 @@ client
|
|
|
====
|
|
|
|
|
|
[[webflux-testing-oauth2-login]]
|
|
|
-=== Testing OAuth 2.0 Login
|
|
|
+== Testing OAuth 2.0 Login
|
|
|
|
|
|
As with <<webflux-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.
|
|
@@ -606,7 +354,7 @@ Further, it also links that `OAuth2User` to a simple instance of `OAuth2Authoriz
|
|
|
This can be handy if your tests <<webflux-testing-oauth2-client,use the `@RegisteredOAuth2AuthorizedClient` annotation>>.
|
|
|
|
|
|
[[webflux-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.
|
|
|
|
|
@@ -635,7 +383,7 @@ client
|
|
|
====
|
|
|
|
|
|
[[webflux-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.
|
|
|
|
|
@@ -689,7 +437,7 @@ client
|
|
|
====
|
|
|
|
|
|
[[webflux-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:
|
|
|
|
|
@@ -733,7 +481,7 @@ client
|
|
|
====
|
|
|
|
|
|
[[webflux-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:
|
|
@@ -846,7 +594,7 @@ assertThat(authorizedClient.accessToken.scopes).containsExactly("read")
|
|
|
The client can then be retrieved as normal using `@RegisteredOAuth2AuthorizedClient` in a controller method.
|
|
|
|
|
|
[[webflux-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:
|
|
@@ -914,7 +662,7 @@ client
|
|
|
====
|
|
|
|
|
|
[[webflux-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:
|
|
|
|
|
@@ -961,7 +709,7 @@ client
|
|
|
====
|
|
|
|
|
|
[[webflux-testing-jwt]]
|
|
|
-=== Testing JWT Authentication
|
|
|
+== Testing JWT Authentication
|
|
|
|
|
|
In order to make an authorized request on a resource server, you need a bearer token.
|
|
|
If your resource server is configured for JWTs, then this would mean that the bearer token needs to be signed and then encoded according to the JWT specification.
|
|
@@ -970,7 +718,7 @@ 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:
|
|
|
|
|
|
-==== `mockJwt() WebTestClientConfigurer`
|
|
|
+=== `mockJwt() WebTestClientConfigurer`
|
|
|
|
|
|
The first way is via a `WebTestClientConfigurer`.
|
|
|
The simplest of these would be to use the `SecurityMockServerConfigurers#mockJwt` method like the following:
|
|
@@ -1144,7 +892,7 @@ client
|
|
|
----
|
|
|
====
|
|
|
|
|
|
-==== `authentication()` `WebTestClientConfigurer`
|
|
|
+=== `authentication()` `WebTestClientConfigurer`
|
|
|
|
|
|
The second way is by using the `authentication()` `Mutator`.
|
|
|
Essentially, you can instantiate your own `JwtAuthenticationToken` and provide it in your test, like so:
|
|
@@ -1184,7 +932,7 @@ client
|
|
|
Note that as an alternative to these, you can also mock the `ReactiveJwtDecoder` bean itself with a `@MockBean` annotation.
|
|
|
|
|
|
[[webflux-testing-opaque-token]]
|
|
|
-=== Testing Opaque Token Authentication
|
|
|
+== Testing Opaque Token Authentication
|
|
|
|
|
|
Similar to <<webflux-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.
|
|
@@ -1270,7 +1018,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.
|
|
|
|
|
|
[[webflux-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.
|
|
|
|
|
@@ -1299,7 +1047,7 @@ client
|
|
|
====
|
|
|
|
|
|
[[webflux-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.
|
|
|
|
|
@@ -1353,7 +1101,7 @@ client
|
|
|
====
|
|
|
|
|
|
[[webflux-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.
|
|
|
|