For example, we can test our example from Chapter 27, EnableReactiveMethodSecurity using the same setup and annotations we did in Section 18.1, “Testing Method Security”. Here is a minimal sample of what we can do:
@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(); } }
Spring Security provides integration with WebTestClient
.
The basic setup looks like this:
@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(); } // ... }
After applying the Spring Security support to WebTestClient
we can use either annotations or mutateWith
support.
For example:
@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!"); }
Spring Security also provides support for CSRF testing with WebTestClient
.
For example:
this.rest // provide a valid CSRF token .mutateWith(csrf()) .post() .uri("/login") ...
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. All of this can be quite daunting, especially when this isn’t the focus of your test.
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:
The first way is via a WebTestClientConfigurer
.
The simplest of these would look something like this:
client
.mutateWith(mockJwt()).get().uri("/endpoint").exchange();
What this will do is create a mock Jwt
, passing it correctly through any authentication APIs so that it’s available for your authorization mechanisms to verify.
By default, the JWT
that it creates has the following characteristics:
{ "headers" : { "alg" : "none" }, "claims" : { "sub" : "user", "scope" : "read" } }
And the resulting Jwt
, were it tested, would pass in the following way:
assertThat(jwt.getTokenValue()).isEqualTo("token"); assertThat(jwt.getHeaders().get("alg")).isEqualTo("none"); assertThat(jwt.getSubject()).isEqualTo("sub"); GrantedAuthority authority = jwt.getAuthorities().iterator().next(); assertThat(authority.getAuthority()).isEqualTo("read");
These values can, of course be configured.
Any headers or claims can be configured with their corresponding methods:
client .mutateWith(mockJwt().jwt(jwt -> jwt.header("kid", "one") .claim("iss", "https://idp.example.org"))) .get().uri("/endpoint").exchange();
client .mutateWith(mockJwt().jwt(jwt -> jwt.claims(claims -> claims.remove("scope")))) .get().uri("/endpoint").exchange();
The scope
and scp
claims are processed the same way here as they are in a normal bearer token request.
However, this can be overridden simply by providing the list of GrantedAuthority
instances that you need for your test:
client .mutateWith(jwt().authorities(new SimpleGrantedAuthority("SCOPE_messages"))) .get().uri("/endpoint").exchange();
Or, if you have a custom Jwt
to Collection<GrantedAuthority>
converter, you can also use that to derive the authorities:
client .mutateWith(jwt().authorities(new MyConverter())) .get().uri("/endpoint").exchange();
You can also specify a complete Jwt
, for which Jwt.Builder
comes quite handy:
Jwt jwt = Jwt.withTokenValue("token") .header("alg", "none") .claim("sub", "user") .claim("scope", "read"); client .mutateWith(mockJwt().jwt(jwt)) .get().uri("/endpoint").exchange();
The second way is by using the authentication()
Mutator
.
Essentially, you can instantiate your own JwtAuthenticationToken
and provide it in your test, like so:
Jwt jwt = Jwt.withTokenValue("token") .header("alg", "none") .claim("sub", "user") .build(); Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("SCOPE_read"); JwtAuthenticationToken token = new JwtAuthenticationToken(jwt, authorities); client .mutateWith(authentication(token)) .get().uri("/endpoint").exchange();
Note that as an alternative to these, you can also mock the ReactiveJwtDecoder
bean itself with a @MockBean
annotation.