1919import java .io .IOException ;
2020import java .time .Duration ;
2121import java .time .Instant ;
22- import java .time .ZoneOffset ;
2322
2423import jakarta .servlet .ServletException ;
2524import jakarta .servlet .http .HttpServletRequest ;
3231import org .springframework .context .annotation .Bean ;
3332import org .springframework .context .annotation .Configuration ;
3433import org .springframework .context .annotation .Import ;
34+ import org .springframework .security .authentication .ott .DefaultOneTimeToken ;
3535import org .springframework .security .authentication .ott .GenerateOneTimeTokenRequest ;
3636import org .springframework .security .authentication .ott .OneTimeToken ;
37+ import org .springframework .security .authentication .ott .OneTimeTokenService ;
3738import org .springframework .security .config .Customizer ;
3839import org .springframework .security .config .annotation .web .builders .HttpSecurity ;
3940import org .springframework .security .config .annotation .web .configuration .EnableWebSecurity ;
4445import org .springframework .security .provisioning .InMemoryUserDetailsManager ;
4546import org .springframework .security .web .SecurityFilterChain ;
4647import org .springframework .security .web .authentication .SimpleUrlAuthenticationSuccessHandler ;
47- import org .springframework .security .web .authentication .ott .DefaultGenerateOneTimeTokenRequestResolver ;
4848import org .springframework .security .web .authentication .ott .GenerateOneTimeTokenRequestResolver ;
4949import org .springframework .security .web .authentication .ott .OneTimeTokenGenerationSuccessHandler ;
5050import org .springframework .security .web .authentication .ott .RedirectOneTimeTokenGenerationSuccessHandler ;
5555
5656import static org .assertj .core .api .Assertions .assertThat ;
5757import static org .assertj .core .api .Assertions .assertThatException ;
58+ import static org .mockito .ArgumentMatchers .any ;
59+ import static org .mockito .ArgumentMatchers .eq ;
60+ import static org .mockito .BDDMockito .given ;
61+ import static org .mockito .Mockito .mock ;
62+ import static org .mockito .Mockito .verify ;
5863import static org .springframework .security .test .web .servlet .request .SecurityMockMvcRequestPostProcessors .csrf ;
5964import static org .springframework .security .test .web .servlet .response .SecurityMockMvcResultMatchers .authenticated ;
6065import static org .springframework .security .test .web .servlet .response .SecurityMockMvcResultMatchers .unauthenticated ;
@@ -72,6 +77,15 @@ public class OneTimeTokenLoginConfigurerTests {
7277 @ Autowired (required = false )
7378 MockMvc mvc ;
7479
80+ @ Autowired (required = false )
81+ private GenerateOneTimeTokenRequestResolver resolver ;
82+
83+ @ Autowired (required = false )
84+ private OneTimeTokenService tokenService ;
85+
86+ @ Autowired (required = false )
87+ private OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler ;
88+
7589 @ Test
7690 void oneTimeTokenWhenCorrectTokenThenCanAuthenticate () throws Exception {
7791 this .spring .register (OneTimeTokenDefaultConfig .class ).autowire ();
@@ -202,21 +216,18 @@ Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
202216
203217 @ Test
204218 void oneTimeTokenWhenCustomTokenExpirationTimeSetThenAuthenticate () throws Exception {
205- this .spring .register (OneTimeTokenConfigWithCustomTokenExpirationTime .class ).autowire ();
206- this .mvc .perform (post ("/ott/generate" ).param ("username" , "user" ).with (csrf ()))
207- .andExpectAll (status ().isFound (), redirectedUrl ("/login/ott" ));
208-
209- OneTimeToken token = getLastToken ();
210-
211- this .mvc .perform (post ("/login/ott" ).param ("token" , token .getTokenValue ()).with (csrf ()))
212- .andExpectAll (status ().isFound (), redirectedUrl ("/" ), authenticated ());
213- assertThat (getCurrentMinutes (token .getExpiresAt ())).isEqualTo (10 );
214- }
215-
216- private int getCurrentMinutes (Instant expiresAt ) {
217- int expiresMinutes = expiresAt .atZone (ZoneOffset .UTC ).getMinute ();
218- int currentMinutes = Instant .now ().atZone (ZoneOffset .UTC ).getMinute ();
219- return expiresMinutes - currentMinutes ;
219+ this .spring .register (OneTimeTokenConfigWithCustomImpls .class ).autowire ();
220+ GenerateOneTimeTokenRequest expectedGenerateRequest = new GenerateOneTimeTokenRequest ("username-123" ,
221+ Duration .ofMinutes (10 ));
222+ OneTimeToken ott = new DefaultOneTimeToken ("token-123" , expectedGenerateRequest .getUsername (),
223+ Instant .now ().plus (expectedGenerateRequest .getExpiresIn ()));
224+ given (this .resolver .resolve (any ())).willReturn (expectedGenerateRequest );
225+ given (this .tokenService .generate (expectedGenerateRequest )).willReturn (ott );
226+ this .mvc .perform (post ("/ott/generate" ).param ("username" , "user" ).with (csrf ()));
227+
228+ verify (this .resolver ).resolve (any ());
229+ verify (this .tokenService ).generate (expectedGenerateRequest );
230+ verify (this .tokenGenerationSuccessHandler ).handle (any (), any (), eq (ott ));
220231 }
221232
222233 private OneTimeToken getLastToken () {
@@ -228,35 +239,40 @@ private OneTimeToken getLastToken() {
228239 @ Configuration (proxyBeanMethods = false )
229240 @ EnableWebSecurity
230241 @ Import (UserDetailsServiceConfig .class )
231- static class OneTimeTokenConfigWithCustomTokenExpirationTime {
242+ static class OneTimeTokenConfigWithCustomImpls {
232243
233244 @ Bean
234245 SecurityFilterChain securityFilterChain (HttpSecurity http ,
246+ GenerateOneTimeTokenRequestResolver ottRequestResolver , OneTimeTokenService ottTokenService ,
235247 OneTimeTokenGenerationSuccessHandler ottSuccessHandler ) throws Exception {
248+
236249 // @formatter:off
237- http
250+ http
238251 .authorizeHttpRequests ((authz ) -> authz
239252 .anyRequest ().authenticated ()
240253 )
241254 .oneTimeTokenLogin ((ott ) -> ott
255+ .generateRequestResolver (ottRequestResolver )
256+ .tokenService (ottTokenService )
242257 .tokenGenerationSuccessHandler (ottSuccessHandler )
243258 );
244259 // @formatter:on
245260 return http .build ();
246261 }
247262
248263 @ Bean
249- TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler () {
250- return new TestOneTimeTokenGenerationSuccessHandler ( );
264+ GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver () {
265+ return mock ( GenerateOneTimeTokenRequestResolver . class );
251266 }
252267
253268 @ Bean
254- GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver () {
255- DefaultGenerateOneTimeTokenRequestResolver delegate = new DefaultGenerateOneTimeTokenRequestResolver ();
256- return (request ) -> {
257- GenerateOneTimeTokenRequest generate = delegate .resolve (request );
258- return new GenerateOneTimeTokenRequest (generate .getUsername (), Duration .ofSeconds (600 ));
259- };
269+ OneTimeTokenService ottService () {
270+ return mock (OneTimeTokenService .class );
271+ }
272+
273+ @ Bean
274+ OneTimeTokenGenerationSuccessHandler ottSuccessHandler () {
275+ return mock (OneTimeTokenGenerationSuccessHandler .class );
260276 }
261277
262278 }
0 commit comments