0
# Reactive Testing (WebTestClient)
1
2
WebTestClient integration for testing security in reactive Spring WebFlux applications, providing mutators for various authentication scenarios including OAuth2, JWT, and OIDC. This integration enables comprehensive testing of reactive security configurations and authentication flows.
3
4
## Setup
5
6
### WebTestClient Configuration
7
8
Configure WebTestClient with Spring Security support for reactive applications.
9
10
```java { .api }
11
/**
12
* Security configurers for reactive testing
13
*/
14
public class SecurityMockServerConfigurers {
15
16
/**
17
* Configure Spring Security with WebTestClient
18
* @return MockServerConfigurer for reactive security integration
19
*/
20
public static MockServerConfigurer springSecurity();
21
}
22
```
23
24
**Usage Examples:**
25
26
```java
27
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
28
public class ReactiveSecurityTest {
29
30
@Autowired
31
private WebTestClient webTestClient;
32
33
// WebTestClient is auto-configured with Spring Security in Spring Boot tests
34
35
@Test
36
public void testReactiveEndpoint() {
37
webTestClient
38
.mutateWith(mockUser())
39
.get().uri("/reactive/secure")
40
.exchange()
41
.expectStatus().isOk();
42
}
43
}
44
```
45
46
## Authentication Mutators
47
48
### Basic User Authentication
49
50
Create user-based authentication for reactive testing.
51
52
```java { .api }
53
/**
54
* Mock authentication with specific Authentication object
55
* @param authentication The authentication to use
56
* @return Mutator that applies the authentication
57
*/
58
public static <T> T mockAuthentication(Authentication authentication);
59
60
/**
61
* Mock user from UserDetails
62
* @param userDetails The UserDetails to use
63
* @return Mutator that applies user authentication
64
*/
65
public static <T> T mockUser(UserDetails userDetails);
66
67
/**
68
* Mock default user with username "user" and role "USER"
69
* @return UserExchangeMutator for configuration
70
*/
71
public static UserExchangeMutator mockUser();
72
73
/**
74
* Mock user with specific username
75
* @param username The username for authentication
76
* @return UserExchangeMutator for configuration
77
*/
78
public static UserExchangeMutator mockUser(String username);
79
80
/**
81
* Fluent configuration for user authentication in reactive tests
82
*/
83
public interface UserExchangeMutator extends WebTestClientConfigurer, MockServerConfigurer {
84
/** Set user password */
85
UserExchangeMutator password(String password);
86
87
/** Set user roles (automatically prefixed with "ROLE_") */
88
UserExchangeMutator roles(String... roles);
89
90
/** Set granted authorities */
91
UserExchangeMutator authorities(GrantedAuthority... authorities);
92
}
93
```
94
95
**Usage Examples:**
96
97
```java
98
@Test
99
public void testWithDefaultUser() {
100
webTestClient
101
.mutateWith(mockUser()) // username="user", roles=["USER"]
102
.get().uri("/api/user-data")
103
.exchange()
104
.expectStatus().isOk();
105
}
106
107
@Test
108
public void testWithCustomUser() {
109
webTestClient
110
.mutateWith(mockUser("alice")
111
.roles("USER", "MANAGER")
112
.password("secret"))
113
.get().uri("/api/manager-data")
114
.exchange()
115
.expectStatus().isOk();
116
}
117
118
@Test
119
public void testWithUserDetails() {
120
UserDetails user = User.builder()
121
.username("bob")
122
.password("password")
123
.authorities("ROLE_ADMIN", "READ_PRIVILEGES")
124
.build();
125
126
webTestClient
127
.mutateWith(mockUser(user))
128
.get().uri("/api/admin-data")
129
.exchange()
130
.expectStatus().isOk();
131
}
132
133
@Test
134
public void testWithCustomAuthentication() {
135
Authentication auth = new UsernamePasswordAuthenticationToken(
136
"customuser",
137
"credentials",
138
Arrays.asList(new SimpleGrantedAuthority("CUSTOM_AUTHORITY"))
139
);
140
141
webTestClient
142
.mutateWith(mockAuthentication(auth))
143
.get().uri("/api/custom")
144
.exchange()
145
.expectStatus().isOk();
146
}
147
```
148
149
### JWT Authentication
150
151
Comprehensive JWT authentication support for reactive applications.
152
153
```java { .api }
154
/**
155
* Mock JWT authentication with default configuration
156
* @return JwtMutator for configuration
157
*/
158
public static JwtMutator mockJwt();
159
160
/**
161
* Fluent configuration for JWT authentication in reactive tests
162
*/
163
public interface JwtMutator extends WebTestClientConfigurer, MockServerConfigurer {
164
/**
165
* Configure JWT using builder consumer
166
* @param jwtBuilderConsumer Consumer to configure JWT builder
167
* @return JwtMutator for chaining
168
*/
169
JwtMutator jwt(Consumer<Jwt.Builder> jwtBuilderConsumer);
170
171
/**
172
* Use specific JWT instance
173
* @param jwt The JWT to use
174
* @return JwtMutator for chaining
175
*/
176
JwtMutator jwt(Jwt jwt);
177
178
/**
179
* Set authorities for JWT authentication
180
* @param authorities The granted authorities
181
* @return JwtMutator for chaining
182
*/
183
JwtMutator authorities(GrantedAuthority... authorities);
184
}
185
```
186
187
**Usage Examples:**
188
189
```java
190
@Test
191
public void testWithJwt() {
192
webTestClient
193
.mutateWith(mockJwt()
194
.jwt(jwt -> jwt
195
.claim("sub", "user123")
196
.claim("scope", "read write")
197
.claim("aud", "my-app"))
198
.authorities(
199
new SimpleGrantedAuthority("SCOPE_read"),
200
new SimpleGrantedAuthority("SCOPE_write")
201
))
202
.get().uri("/api/jwt-protected")
203
.exchange()
204
.expectStatus().isOk();
205
}
206
207
@Test
208
public void testWithPrebuiltJwt() {
209
Jwt jwt = Jwt.withTokenValue("token-value")
210
.header("alg", "HS256")
211
.claim("sub", "user456")
212
.claim("scope", "admin")
213
.build();
214
215
webTestClient
216
.mutateWith(mockJwt().jwt(jwt))
217
.get().uri("/api/admin-jwt")
218
.exchange()
219
.expectStatus().isOk();
220
}
221
```
222
223
### OAuth2 Opaque Token
224
225
Support for OAuth2 opaque token authentication in reactive applications.
226
227
```java { .api }
228
/**
229
* Mock OAuth2 opaque token authentication
230
* @return OpaqueTokenMutator for configuration
231
*/
232
public static OpaqueTokenMutator mockOpaqueToken();
233
234
/**
235
* Fluent configuration for opaque token authentication
236
*/
237
public interface OpaqueTokenMutator extends WebTestClientConfigurer, MockServerConfigurer {
238
/**
239
* Set token attributes
240
* @param attributes Map of token attributes
241
* @return OpaqueTokenMutator for chaining
242
*/
243
OpaqueTokenMutator attributes(Map<String, Object> attributes);
244
245
/**
246
* Configure token attributes using consumer
247
* @param attributesConsumer Consumer to configure attributes
248
* @return OpaqueTokenMutator for chaining
249
*/
250
OpaqueTokenMutator attributes(Consumer<Map<String, Object>> attributesConsumer);
251
252
/**
253
* Set authorities for token authentication
254
* @param authorities The granted authorities
255
* @return OpaqueTokenMutator for chaining
256
*/
257
OpaqueTokenMutator authorities(GrantedAuthority... authorities);
258
}
259
```
260
261
**Usage Examples:**
262
263
```java
264
@Test
265
public void testWithOpaqueToken() {
266
webTestClient
267
.mutateWith(mockOpaqueToken()
268
.attributes(attrs -> {
269
attrs.put("sub", "user789");
270
attrs.put("scope", "read write");
271
attrs.put("client_id", "my-client");
272
})
273
.authorities(
274
new SimpleGrantedAuthority("SCOPE_read"),
275
new SimpleGrantedAuthority("SCOPE_write")
276
))
277
.get().uri("/api/token-secured")
278
.exchange()
279
.expectStatus().isOk();
280
}
281
```
282
283
### OAuth2 Login
284
285
OAuth2 login authentication support for reactive applications.
286
287
```java { .api }
288
/**
289
* Mock OAuth2 login authentication
290
* @return OAuth2LoginMutator for configuration
291
*/
292
public static OAuth2LoginMutator mockOAuth2Login();
293
294
/**
295
* Fluent configuration for OAuth2 login authentication
296
*/
297
public interface OAuth2LoginMutator extends WebTestClientConfigurer, MockServerConfigurer {
298
/**
299
* Set OAuth2 user attributes
300
* @param attributes Map of user attributes
301
* @return OAuth2LoginMutator for chaining
302
*/
303
OAuth2LoginMutator attributes(Map<String, Object> attributes);
304
305
/**
306
* Configure OAuth2 user attributes using consumer
307
* @param attributesConsumer Consumer to configure attributes
308
* @return OAuth2LoginMutator for chaining
309
*/
310
OAuth2LoginMutator attributes(Consumer<Map<String, Object>> attributesConsumer);
311
312
/**
313
* Set client registration ID
314
* @param clientRegistrationId The client registration ID
315
* @return OAuth2LoginMutator for chaining
316
*/
317
OAuth2LoginMutator clientRegistrationId(String clientRegistrationId);
318
319
/**
320
* Set authorities for OAuth2 authentication
321
* @param authorities The granted authorities
322
* @return OAuth2LoginMutator for chaining
323
*/
324
OAuth2LoginMutator authorities(GrantedAuthority... authorities);
325
}
326
```
327
328
**Usage Examples:**
329
330
```java
331
@Test
332
public void testWithOAuth2Login() {
333
webTestClient
334
.mutateWith(mockOAuth2Login()
335
.clientRegistrationId("google")
336
.attributes(attrs -> {
337
attrs.put("sub", "12345");
338
attrs.put("name", "John Doe");
339
attrs.put("email", "john@example.com");
340
})
341
.authorities(new SimpleGrantedAuthority("ROLE_USER")))
342
.get().uri("/oauth2/user")
343
.exchange()
344
.expectStatus().isOk();
345
}
346
```
347
348
### OIDC Login
349
350
OpenID Connect login authentication support for reactive applications.
351
352
```java { .api }
353
/**
354
* Mock OIDC login authentication
355
* @return OidcLoginMutator for configuration
356
*/
357
public static OidcLoginMutator mockOidcLogin();
358
359
/**
360
* Fluent configuration for OIDC login authentication
361
*/
362
public interface OidcLoginMutator extends WebTestClientConfigurer, MockServerConfigurer {
363
/**
364
* Configure ID token
365
* @param idTokenBuilderConsumer Consumer to configure ID token
366
* @return OidcLoginMutator for chaining
367
*/
368
OidcLoginMutator idToken(Consumer<OidcIdToken.Builder> idTokenBuilderConsumer);
369
370
/**
371
* Configure user info
372
* @param userInfoConsumer Consumer to configure user info
373
* @return OidcLoginMutator for chaining
374
*/
375
OidcLoginMutator userInfo(Consumer<Map<String, Object>> userInfoConsumer);
376
377
/**
378
* Set client registration ID
379
* @param clientRegistrationId The client registration ID
380
* @return OidcLoginMutator for chaining
381
*/
382
OidcLoginMutator clientRegistrationId(String clientRegistrationId);
383
384
/**
385
* Set authorities for OIDC authentication
386
* @param authorities The granted authorities
387
* @return OidcLoginMutator for chaining
388
*/
389
OidcLoginMutator authorities(GrantedAuthority... authorities);
390
}
391
```
392
393
**Usage Examples:**
394
395
```java
396
@Test
397
public void testWithOidcLogin() {
398
webTestClient
399
.mutateWith(mockOidcLogin()
400
.clientRegistrationId("auth0")
401
.idToken(token -> token
402
.claim("sub", "user123")
403
.claim("email", "user@example.com")
404
.claim("email_verified", true))
405
.userInfo(userInfo -> {
406
userInfo.put("given_name", "John");
407
userInfo.put("family_name", "Doe");
408
userInfo.put("picture", "https://example.com/avatar.jpg");
409
})
410
.authorities(
411
new SimpleGrantedAuthority("ROLE_USER"),
412
new SimpleGrantedAuthority("SCOPE_openid")
413
))
414
.get().uri("/oidc/userinfo")
415
.exchange()
416
.expectStatus().isOk();
417
}
418
```
419
420
### OAuth2 Client
421
422
OAuth2 client configuration for reactive applications.
423
424
```java { .api }
425
/**
426
* Mock OAuth2 client with default configuration
427
* @return OAuth2ClientMutator for configuration
428
*/
429
public static OAuth2ClientMutator mockOAuth2Client();
430
431
/**
432
* Mock OAuth2 client with specific registration ID
433
* @param registrationId The client registration ID
434
* @return OAuth2ClientMutator for configuration
435
*/
436
public static OAuth2ClientMutator mockOAuth2Client(String registrationId);
437
438
/**
439
* Fluent configuration for OAuth2 client
440
*/
441
public interface OAuth2ClientMutator extends WebTestClientConfigurer, MockServerConfigurer {
442
/**
443
* Set client registration ID
444
* @param registrationId The client registration ID
445
* @return OAuth2ClientMutator for chaining
446
*/
447
OAuth2ClientMutator registrationId(String registrationId);
448
449
/**
450
* Set access token
451
* @param accessToken The access token
452
* @return OAuth2ClientMutator for chaining
453
*/
454
OAuth2ClientMutator accessToken(OAuth2AccessToken accessToken);
455
456
/**
457
* Configure access token using consumer
458
* @param accessTokenConsumer Consumer to configure access token
459
* @return OAuth2ClientMutator for chaining
460
*/
461
OAuth2ClientMutator accessToken(Consumer<OAuth2AccessToken.Builder> accessTokenConsumer);
462
}
463
```
464
465
**Usage Examples:**
466
467
```java
468
@Test
469
public void testWithOAuth2Client() {
470
webTestClient
471
.mutateWith(mockOAuth2Client("api-client")
472
.accessToken(token -> token
473
.tokenType(OAuth2AccessToken.TokenType.BEARER)
474
.scopes(Set.of("read", "write"))
475
.tokenValue("access-token-value")))
476
.get().uri("/api/client-data")
477
.exchange()
478
.expectStatus().isOk();
479
}
480
```
481
482
### CSRF Protection
483
484
CSRF token support for reactive applications.
485
486
```java { .api }
487
/**
488
* Mock CSRF support for reactive applications
489
* @return CsrfMutator for configuration
490
*/
491
public static CsrfMutator csrf();
492
493
/**
494
* Fluent configuration for CSRF in reactive tests
495
*/
496
public interface CsrfMutator extends WebTestClientConfigurer, MockServerConfigurer {
497
// CSRF-specific configuration methods for reactive testing
498
}
499
```
500
501
**Usage Examples:**
502
503
```java
504
@Test
505
public void testCsrfProtectedEndpoint() {
506
webTestClient
507
.mutateWith(csrf())
508
.mutateWith(mockUser())
509
.post().uri("/api/form-submit")
510
.bodyValue("data=test")
511
.exchange()
512
.expectStatus().isOk();
513
}
514
```
515
516
## Common Reactive Testing Patterns
517
518
### Comprehensive Reactive Security Test
519
520
```java
521
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
522
public class ReactiveSecurityIntegrationTest {
523
524
@Autowired
525
private WebTestClient webTestClient;
526
527
@Test
528
public void testUnauthenticatedAccess() {
529
webTestClient
530
.get().uri("/api/public")
531
.exchange()
532
.expectStatus().isOk();
533
534
webTestClient
535
.get().uri("/api/secure")
536
.exchange()
537
.expectStatus().isUnauthorized();
538
}
539
540
@Test
541
public void testUserAuthentication() {
542
webTestClient
543
.mutateWith(mockUser("testuser").roles("USER"))
544
.get().uri("/api/user-data")
545
.exchange()
546
.expectStatus().isOk()
547
.expectBody(String.class)
548
.value(response -> {
549
assertThat(response).contains("testuser");
550
});
551
}
552
553
@Test
554
public void testAdminOnlyEndpoint() {
555
// Test user access - should be forbidden
556
webTestClient
557
.mutateWith(mockUser("user").roles("USER"))
558
.get().uri("/api/admin")
559
.exchange()
560
.expectStatus().isForbidden();
561
562
// Test admin access - should succeed
563
webTestClient
564
.mutateWith(mockUser("admin").roles("ADMIN"))
565
.get().uri("/api/admin")
566
.exchange()
567
.expectStatus().isOk();
568
}
569
570
@Test
571
public void testJwtAuthentication() {
572
webTestClient
573
.mutateWith(mockJwt()
574
.jwt(jwt -> jwt
575
.claim("sub", "jwt-user")
576
.claim("scope", "read write"))
577
.authorities(
578
new SimpleGrantedAuthority("SCOPE_read"),
579
new SimpleGrantedAuthority("SCOPE_write")
580
))
581
.get().uri("/api/jwt-data")
582
.exchange()
583
.expectStatus().isOk();
584
}
585
586
@Test
587
public void testOAuth2Login() {
588
webTestClient
589
.mutateWith(mockOAuth2Login()
590
.attributes(attrs -> {
591
attrs.put("sub", "oauth-user");
592
attrs.put("name", "OAuth User");
593
attrs.put("email", "oauth@example.com");
594
}))
595
.get().uri("/oauth2/profile")
596
.exchange()
597
.expectStatus().isOk()
598
.expectBody()
599
.jsonPath("$.name").isEqualTo("OAuth User")
600
.jsonPath("$.email").isEqualTo("oauth@example.com");
601
}
602
603
@Test
604
public void testReactiveCsrfProtection() {
605
webTestClient
606
.mutateWith(csrf())
607
.mutateWith(mockUser())
608
.post().uri("/api/form")
609
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
610
.bodyValue("field=value")
611
.exchange()
612
.expectStatus().isOk();
613
}
614
}
615
```
616
617
### Testing Reactive Security Configurations
618
619
```java
620
@Test
621
public void testSecurityConfiguration() {
622
// Test method security
623
webTestClient
624
.mutateWith(mockUser("user").roles("USER"))
625
.get().uri("/api/user-method")
626
.exchange()
627
.expectStatus().isOk();
628
629
webTestClient
630
.mutateWith(mockUser("user").roles("USER"))
631
.get().uri("/api/admin-method")
632
.exchange()
633
.expectStatus().isForbidden();
634
635
// Test reactive security context
636
webTestClient
637
.mutateWith(mockUser("testuser"))
638
.get().uri("/api/current-user")
639
.exchange()
640
.expectStatus().isOk()
641
.expectBody(String.class)
642
.isEqualTo("testuser");
643
}
644
```
645
646
### Testing Error Scenarios
647
648
```java
649
@Test
650
public void testAuthenticationFailures() {
651
// Test expired JWT
652
Jwt expiredJwt = Jwt.withTokenValue("expired-token")
653
.header("alg", "HS256")
654
.claim("sub", "user")
655
.claim("exp", Instant.now().minusSeconds(3600)) // Expired
656
.build();
657
658
webTestClient
659
.mutateWith(mockJwt().jwt(expiredJwt))
660
.get().uri("/api/secure")
661
.exchange()
662
.expectStatus().isUnauthorized();
663
664
// Test insufficient authorities
665
webTestClient
666
.mutateWith(mockUser("user").roles("USER"))
667
.get().uri("/api/admin-only")
668
.exchange()
669
.expectStatus().isForbidden();
670
}
671
```