1
1
/*
2
- * Copyright 2002-2024 the original author or authors.
2
+ * Copyright 2002-2025 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
21
21
22
22
import org .junit .jupiter .api .Test ;
23
23
import org .junit .jupiter .api .extension .ExtendWith ;
24
+ import org .mockito .ArgumentMatchers ;
25
+ import org .mockito .Mockito ;
24
26
import reactor .core .publisher .Mono ;
25
27
26
28
import org .springframework .beans .factory .annotation .Autowired ;
40
42
import org .springframework .security .test .web .reactive .server .SecurityMockServerConfigurers ;
41
43
import org .springframework .security .web .server .SecurityWebFilterChain ;
42
44
import org .springframework .security .web .server .authentication .RedirectServerAuthenticationSuccessHandler ;
45
+ import org .springframework .security .web .server .authentication .ott .DefaultServerGenerateOneTimeTokenRequestResolver ;
46
+ import org .springframework .security .web .server .authentication .ott .ServerGenerateOneTimeTokenRequestResolver ;
43
47
import org .springframework .security .web .server .authentication .ott .ServerOneTimeTokenGenerationSuccessHandler ;
44
48
import org .springframework .security .web .server .authentication .ott .ServerRedirectOneTimeTokenGenerationSuccessHandler ;
45
49
import org .springframework .test .web .reactive .server .WebTestClient ;
49
53
50
54
import static org .assertj .core .api .Assertions .assertThat ;
51
55
import static org .assertj .core .api .Assertions .assertThatException ;
56
+ import static org .mockito .Mockito .times ;
57
+ import static org .mockito .Mockito .verify ;
52
58
53
59
/**
54
60
* Tests for {@link ServerHttpSecurity.OneTimeTokenLoginSpec}
@@ -107,7 +113,7 @@ void oneTimeTokenWhenCorrectTokenThenCanAuthenticate() {
107
113
.expectHeader ().valueEquals ("Location" , "/login/ott" );
108
114
// @formatter:on
109
115
110
- String token = TestServerOneTimeTokenGenerationSuccessHandler . lastToken .getTokenValue ();
116
+ String token = getLastToken () .getTokenValue ();
111
117
112
118
// @formatter:off
113
119
this .client .mutateWith (SecurityMockServerConfigurers .csrf ())
@@ -143,7 +149,7 @@ void oneTimeTokenWhenDifferentAuthenticationUrlsThenCanAuthenticate() {
143
149
.expectHeader ().valueEquals ("Location" , "/redirected" );
144
150
// @formatter:on
145
151
146
- String token = TestServerOneTimeTokenGenerationSuccessHandler . lastToken .getTokenValue ();
152
+ String token = getLastToken () .getTokenValue ();
147
153
148
154
// @formatter:off
149
155
this .client .mutateWith (SecurityMockServerConfigurers .csrf ())
@@ -179,7 +185,7 @@ void oneTimeTokenWhenCorrectTokenUsedTwiceThenSecondTimeFails() {
179
185
.expectHeader ().valueEquals ("Location" , "/login/ott" );
180
186
// @formatter:on
181
187
182
- String token = TestServerOneTimeTokenGenerationSuccessHandler . lastToken .getTokenValue ();
188
+ String token = getLastToken () .getTokenValue ();
183
189
184
190
// @formatter:off
185
191
this .client .mutateWith (SecurityMockServerConfigurers .csrf ())
@@ -268,39 +274,76 @@ void oneTimeTokenWhenFormLoginConfiguredThenRendersRequestTokenForm() {
268
274
assertThat (response .contains (GENERATE_OTT_PART )).isTrue ();
269
275
}
270
276
277
+ private OneTimeToken getLastToken () {
278
+ OneTimeToken lastToken = this .spring .getContext ()
279
+ .getBean (TestServerOneTimeTokenGenerationSuccessHandler .class ).lastToken ;
280
+ return lastToken ;
281
+ }
282
+
271
283
@ Test
272
284
void oneTimeTokenWhenNoOneTimeTokenGenerationSuccessHandlerThenException () {
273
285
assertThatException ()
274
- .isThrownBy (() -> this .spring .register (OneTimeTokenNotGeneratedOttHandlerConfig .class ).autowire ())
275
- .havingRootCause ()
276
- .isInstanceOf (IllegalStateException .class )
277
- .withMessage ("""
286
+ .isThrownBy (() -> this .spring .register (OneTimeTokenNotGeneratedOttHandlerConfig .class ).autowire ())
287
+ .havingRootCause ()
288
+ .isInstanceOf (IllegalStateException .class )
289
+ .withMessage ("""
278
290
A ServerOneTimeTokenGenerationSuccessHandler is required to enable oneTimeTokenLogin().
279
291
Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
280
292
""" );
281
293
}
282
294
295
+ @ Test
296
+ void oneTimeTokenWhenCustomRequestResolverSetThenCustomResolverUse () {
297
+ this .spring .register (OneTimeTokenConfigWithCustomRequestResolver .class ).autowire ();
298
+
299
+ // @formatter:off
300
+ this .client .mutateWith (SecurityMockServerConfigurers .csrf ())
301
+ .post ()
302
+ .uri ((uriBuilder ) -> uriBuilder
303
+ .path ("/ott/generate" )
304
+ .build ()
305
+ )
306
+ .contentType (MediaType .APPLICATION_FORM_URLENCODED )
307
+ .body (BodyInserters .fromFormData ("username" , "user" ))
308
+ .exchange ()
309
+ .expectStatus ()
310
+ .is3xxRedirection ()
311
+ .expectHeader ().valueEquals ("Location" , "/login/ott" );
312
+ // @formatter:on
313
+
314
+ ServerGenerateOneTimeTokenRequestResolver resolver = this .spring .getContext ()
315
+ .getBean (ServerGenerateOneTimeTokenRequestResolver .class );
316
+
317
+ verify (resolver , times (1 )).resolve (ArgumentMatchers .any (ServerWebExchange .class ));
318
+ }
319
+
283
320
@ Configuration (proxyBeanMethods = false )
284
321
@ EnableWebFlux
285
322
@ EnableWebFluxSecurity
286
323
@ Import (UserDetailsServiceConfig .class )
287
324
static class OneTimeTokenDefaultConfig {
288
325
289
326
@ Bean
290
- SecurityWebFilterChain securityWebFilterChain (ServerHttpSecurity http ) {
327
+ SecurityWebFilterChain securityWebFilterChain (ServerHttpSecurity http ,
328
+ ServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler ) {
291
329
// @formatter:off
292
330
http
293
331
.authorizeExchange ((authorize ) -> authorize
294
332
.anyExchange ()
295
333
.authenticated ()
296
334
)
297
335
.oneTimeTokenLogin ((ott ) -> ott
298
- .tokenGenerationSuccessHandler (new TestServerOneTimeTokenGenerationSuccessHandler () )
336
+ .tokenGenerationSuccessHandler (ottSuccessHandler )
299
337
);
300
338
// @formatter:on
301
339
return http .build ();
302
340
}
303
341
342
+ @ Bean
343
+ TestServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler () {
344
+ return new TestServerOneTimeTokenGenerationSuccessHandler ();
345
+ }
346
+
304
347
}
305
348
306
349
@ Configuration (proxyBeanMethods = false )
@@ -310,7 +353,8 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
310
353
static class OneTimeTokenDifferentUrlsConfig {
311
354
312
355
@ Bean
313
- SecurityWebFilterChain securityFilterChain (ServerHttpSecurity http ) {
356
+ SecurityWebFilterChain securityFilterChain (ServerHttpSecurity http ,
357
+ ServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler ) {
314
358
// @formatter:off
315
359
http
316
360
.authorizeExchange ((authorize ) -> authorize
@@ -319,14 +363,19 @@ SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
319
363
)
320
364
.oneTimeTokenLogin ((ott ) -> ott
321
365
.tokenGeneratingUrl ("/generateurl" )
322
- .tokenGenerationSuccessHandler (new TestServerOneTimeTokenGenerationSuccessHandler ( "/redirected" ) )
366
+ .tokenGenerationSuccessHandler (ottSuccessHandler )
323
367
.loginProcessingUrl ("/loginprocessingurl" )
324
368
.authenticationSuccessHandler (new RedirectServerAuthenticationSuccessHandler ("/authenticated" ))
325
369
);
326
370
// @formatter:on
327
371
return http .build ();
328
372
}
329
373
374
+ @ Bean
375
+ TestServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler () {
376
+ return new TestServerOneTimeTokenGenerationSuccessHandler ("/redirected" );
377
+ }
378
+
330
379
}
331
380
332
381
@ Configuration (proxyBeanMethods = false )
@@ -336,7 +385,8 @@ SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
336
385
static class OneTimeTokenFormLoginConfig {
337
386
338
387
@ Bean
339
- SecurityWebFilterChain securityFilterChain (ServerHttpSecurity http ) {
388
+ SecurityWebFilterChain securityFilterChain (ServerHttpSecurity http ,
389
+ ServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler ) {
340
390
// @formatter:off
341
391
http
342
392
.authorizeExchange ((authorize ) -> authorize
@@ -345,12 +395,17 @@ SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
345
395
)
346
396
.formLogin (Customizer .withDefaults ())
347
397
.oneTimeTokenLogin ((ott ) -> ott
348
- .tokenGenerationSuccessHandler (new TestServerOneTimeTokenGenerationSuccessHandler () )
398
+ .tokenGenerationSuccessHandler (ottSuccessHandler )
349
399
);
350
400
// @formatter:on
351
401
return http .build ();
352
402
}
353
403
404
+ @ Bean
405
+ TestServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler () {
406
+ return new TestServerOneTimeTokenGenerationSuccessHandler ();
407
+ }
408
+
354
409
}
355
410
356
411
@ Configuration (proxyBeanMethods = false )
@@ -385,10 +440,44 @@ ReactiveUserDetailsService userDetailsService() {
385
440
386
441
}
387
442
443
+ @ Configuration (proxyBeanMethods = false )
444
+ @ EnableWebFlux
445
+ @ EnableWebFluxSecurity
446
+ @ Import (UserDetailsServiceConfig .class )
447
+ static class OneTimeTokenConfigWithCustomRequestResolver {
448
+
449
+ @ Bean
450
+ SecurityWebFilterChain securityWebFilterChain (ServerHttpSecurity http ,
451
+ ServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler ) {
452
+ // @formatter:off
453
+ http
454
+ .authorizeExchange ((authorize ) -> authorize
455
+ .anyExchange ()
456
+ .authenticated ()
457
+ )
458
+ .oneTimeTokenLogin ((ott ) -> ott
459
+ .tokenGenerationSuccessHandler (ottSuccessHandler )
460
+ );
461
+ // @formatter:on
462
+ return http .build ();
463
+ }
464
+
465
+ @ Bean
466
+ ServerGenerateOneTimeTokenRequestResolver resolver () {
467
+ return Mockito .spy (new DefaultServerGenerateOneTimeTokenRequestResolver ());
468
+ }
469
+
470
+ @ Bean
471
+ TestServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler () {
472
+ return new TestServerOneTimeTokenGenerationSuccessHandler ();
473
+ }
474
+
475
+ }
476
+
388
477
private static class TestServerOneTimeTokenGenerationSuccessHandler
389
478
implements ServerOneTimeTokenGenerationSuccessHandler {
390
479
391
- private static OneTimeToken lastToken ;
480
+ private OneTimeToken lastToken ;
392
481
393
482
private final ServerOneTimeTokenGenerationSuccessHandler delegate ;
394
483
@@ -402,7 +491,7 @@ private static class TestServerOneTimeTokenGenerationSuccessHandler
402
491
403
492
@ Override
404
493
public Mono <Void > handle (ServerWebExchange exchange , OneTimeToken oneTimeToken ) {
405
- lastToken = oneTimeToken ;
494
+ this . lastToken = oneTimeToken ;
406
495
return this .delegate .handle (exchange , oneTimeToken );
407
496
}
408
497
0 commit comments