2
0
Эх сурвалжийг харах

TestServerOneTimeTokenGenerationSuccessHandler.lastToken to non-static variable

Signed-off-by: Max Batischev <mblancer@mail.ru>
Max Batischev 6 сар өмнө
parent
commit
9676739c88

+ 105 - 16
config/src/test/java/org/springframework/security/config/web/server/OneTimeTokenLoginSpecTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@ import java.util.Map;
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
 import reactor.core.publisher.Mono;
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -40,6 +42,8 @@ import org.springframework.security.core.userdetails.User;
 import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
+import org.springframework.security.web.server.authentication.ott.DefaultServerGenerateOneTimeTokenRequestResolver;
+import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver;
 import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler;
 import org.springframework.security.web.server.authentication.ott.ServerRedirectOneTimeTokenGenerationSuccessHandler;
 import org.springframework.test.web.reactive.server.WebTestClient;
@@ -49,6 +53,8 @@ import org.springframework.web.server.ServerWebExchange;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatException;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 /**
  * Tests for {@link ServerHttpSecurity.OneTimeTokenLoginSpec}
@@ -107,7 +113,7 @@ public class OneTimeTokenLoginSpecTests {
 				.expectHeader().valueEquals("Location", "/login/ott");
 		// @formatter:on
 
-		String token = TestServerOneTimeTokenGenerationSuccessHandler.lastToken.getTokenValue();
+		String token = getLastToken().getTokenValue();
 
 		// @formatter:off
 		this.client.mutateWith(SecurityMockServerConfigurers.csrf())
@@ -143,7 +149,7 @@ public class OneTimeTokenLoginSpecTests {
 				.expectHeader().valueEquals("Location", "/redirected");
 		// @formatter:on
 
-		String token = TestServerOneTimeTokenGenerationSuccessHandler.lastToken.getTokenValue();
+		String token = getLastToken().getTokenValue();
 
 		// @formatter:off
 		this.client.mutateWith(SecurityMockServerConfigurers.csrf())
@@ -179,7 +185,7 @@ public class OneTimeTokenLoginSpecTests {
 				.expectHeader().valueEquals("Location", "/login/ott");
 		// @formatter:on
 
-		String token = TestServerOneTimeTokenGenerationSuccessHandler.lastToken.getTokenValue();
+		String token = getLastToken().getTokenValue();
 
 		// @formatter:off
 		this.client.mutateWith(SecurityMockServerConfigurers.csrf())
@@ -268,18 +274,49 @@ public class OneTimeTokenLoginSpecTests {
 		assertThat(response.contains(GENERATE_OTT_PART)).isTrue();
 	}
 
+	private OneTimeToken getLastToken() {
+		OneTimeToken lastToken = this.spring.getContext()
+			.getBean(TestServerOneTimeTokenGenerationSuccessHandler.class).lastToken;
+		return lastToken;
+	}
+
 	@Test
 	void oneTimeTokenWhenNoOneTimeTokenGenerationSuccessHandlerThenException() {
 		assertThatException()
-				.isThrownBy(() -> this.spring.register(OneTimeTokenNotGeneratedOttHandlerConfig.class).autowire())
-				.havingRootCause()
-				.isInstanceOf(IllegalStateException.class)
-				.withMessage("""
+			.isThrownBy(() -> this.spring.register(OneTimeTokenNotGeneratedOttHandlerConfig.class).autowire())
+			.havingRootCause()
+			.isInstanceOf(IllegalStateException.class)
+			.withMessage("""
 					A ServerOneTimeTokenGenerationSuccessHandler is required to enable oneTimeTokenLogin().
 					Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
 					""");
 	}
 
+	@Test
+	void oneTimeTokenWhenCustomRequestResolverSetThenCustomResolverUse() {
+		this.spring.register(OneTimeTokenConfigWithCustomRequestResolver.class).autowire();
+
+		// @formatter:off
+		this.client.mutateWith(SecurityMockServerConfigurers.csrf())
+				.post()
+				.uri((uriBuilder) -> uriBuilder
+						.path("/ott/generate")
+						.build()
+				)
+				.contentType(MediaType.APPLICATION_FORM_URLENCODED)
+				.body(BodyInserters.fromFormData("username", "user"))
+				.exchange()
+				.expectStatus()
+				.is3xxRedirection()
+				.expectHeader().valueEquals("Location", "/login/ott");
+		// @formatter:on
+
+		ServerGenerateOneTimeTokenRequestResolver resolver = this.spring.getContext()
+			.getBean(ServerGenerateOneTimeTokenRequestResolver.class);
+
+		verify(resolver, times(1)).resolve(ArgumentMatchers.any(ServerWebExchange.class));
+	}
+
 	@Configuration(proxyBeanMethods = false)
 	@EnableWebFlux
 	@EnableWebFluxSecurity
@@ -287,7 +324,8 @@ public class OneTimeTokenLoginSpecTests {
 	static class OneTimeTokenDefaultConfig {
 
 		@Bean
-		SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
+		SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http,
+				ServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler) {
 			// @formatter:off
 			http
 					.authorizeExchange((authorize) -> authorize
@@ -295,12 +333,17 @@ public class OneTimeTokenLoginSpecTests {
 							.authenticated()
 					)
 					.oneTimeTokenLogin((ott) -> ott
-							.tokenGenerationSuccessHandler(new TestServerOneTimeTokenGenerationSuccessHandler())
+							.tokenGenerationSuccessHandler(ottSuccessHandler)
 					);
 			// @formatter:on
 			return http.build();
 		}
 
+		@Bean
+		TestServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
+			return new TestServerOneTimeTokenGenerationSuccessHandler();
+		}
+
 	}
 
 	@Configuration(proxyBeanMethods = false)
@@ -310,7 +353,8 @@ public class OneTimeTokenLoginSpecTests {
 	static class OneTimeTokenDifferentUrlsConfig {
 
 		@Bean
-		SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
+		SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http,
+				ServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler) {
 			// @formatter:off
 			http
 					.authorizeExchange((authorize) -> authorize
@@ -319,7 +363,7 @@ public class OneTimeTokenLoginSpecTests {
 					)
 					.oneTimeTokenLogin((ott) -> ott
 							.tokenGeneratingUrl("/generateurl")
-							.tokenGenerationSuccessHandler(new TestServerOneTimeTokenGenerationSuccessHandler("/redirected"))
+							.tokenGenerationSuccessHandler(ottSuccessHandler)
 							.loginProcessingUrl("/loginprocessingurl")
 							.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/authenticated"))
 					);
@@ -327,6 +371,11 @@ public class OneTimeTokenLoginSpecTests {
 			return http.build();
 		}
 
+		@Bean
+		TestServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
+			return new TestServerOneTimeTokenGenerationSuccessHandler("/redirected");
+		}
+
 	}
 
 	@Configuration(proxyBeanMethods = false)
@@ -336,7 +385,8 @@ public class OneTimeTokenLoginSpecTests {
 	static class OneTimeTokenFormLoginConfig {
 
 		@Bean
-		SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
+		SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http,
+				ServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler) {
 			// @formatter:off
 			http
 					.authorizeExchange((authorize) -> authorize
@@ -345,12 +395,17 @@ public class OneTimeTokenLoginSpecTests {
 					)
 					.formLogin(Customizer.withDefaults())
 					.oneTimeTokenLogin((ott) -> ott
-							.tokenGenerationSuccessHandler(new TestServerOneTimeTokenGenerationSuccessHandler())
+							.tokenGenerationSuccessHandler(ottSuccessHandler)
 					);
 			// @formatter:on
 			return http.build();
 		}
 
+		@Bean
+		TestServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
+			return new TestServerOneTimeTokenGenerationSuccessHandler();
+		}
+
 	}
 
 	@Configuration(proxyBeanMethods = false)
@@ -385,10 +440,44 @@ public class OneTimeTokenLoginSpecTests {
 
 	}
 
+	@Configuration(proxyBeanMethods = false)
+	@EnableWebFlux
+	@EnableWebFluxSecurity
+	@Import(UserDetailsServiceConfig.class)
+	static class OneTimeTokenConfigWithCustomRequestResolver {
+
+		@Bean
+		SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http,
+				ServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler) {
+			// @formatter:off
+			http
+					.authorizeExchange((authorize) -> authorize
+							.anyExchange()
+							.authenticated()
+					)
+					.oneTimeTokenLogin((ott) -> ott
+							.tokenGenerationSuccessHandler(ottSuccessHandler)
+					);
+			// @formatter:on
+			return http.build();
+		}
+
+		@Bean
+		ServerGenerateOneTimeTokenRequestResolver resolver() {
+			return Mockito.spy(new DefaultServerGenerateOneTimeTokenRequestResolver());
+		}
+
+		@Bean
+		TestServerOneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
+			return new TestServerOneTimeTokenGenerationSuccessHandler();
+		}
+
+	}
+
 	private static class TestServerOneTimeTokenGenerationSuccessHandler
 			implements ServerOneTimeTokenGenerationSuccessHandler {
 
-		private static OneTimeToken lastToken;
+		private OneTimeToken lastToken;
 
 		private final ServerOneTimeTokenGenerationSuccessHandler delegate;
 
@@ -402,7 +491,7 @@ public class OneTimeTokenLoginSpecTests {
 
 		@Override
 		public Mono<Void> handle(ServerWebExchange exchange, OneTimeToken oneTimeToken) {
-			lastToken = oneTimeToken;
+			this.lastToken = oneTimeToken;
 			return this.delegate.handle(exchange, oneTimeToken);
 		}
 

+ 45 - 52
config/src/test/kotlin/org/springframework/security/config/web/server/ServerOneTimeTokenLoginDslTests.kt

@@ -16,18 +16,17 @@
 
 package org.springframework.security.config.web.server
 
-import org.assertj.core.api.Assertions
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
-import reactor.core.publisher.Mono
-
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
 import org.springframework.context.annotation.Import
-import org.springframework.context.ApplicationContext
 import org.springframework.http.MediaType
-import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest
 import org.springframework.security.authentication.ott.OneTimeToken
 import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
 import org.springframework.security.config.test.SpringTestContext
@@ -36,10 +35,10 @@ import org.springframework.security.core.userdetails.MapReactiveUserDetailsServi
 import org.springframework.security.core.userdetails.ReactiveUserDetailsService
 import org.springframework.security.core.userdetails.User
 import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers
-import org.springframework.security.web.server.authentication.ott.DefaultServerGenerateOneTimeTokenRequestResolver
-import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver
 import org.springframework.security.web.server.SecurityWebFilterChain
 import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler
+import org.springframework.security.web.server.authentication.ott.DefaultServerGenerateOneTimeTokenRequestResolver
+import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver
 import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler
 import org.springframework.security.web.server.authentication.ott.ServerRedirectOneTimeTokenGenerationSuccessHandler
 import org.springframework.test.web.reactive.server.WebTestClient
@@ -47,9 +46,7 @@ import org.springframework.web.reactive.config.EnableWebFlux
 import org.springframework.web.reactive.function.BodyInserters
 import org.springframework.web.server.ServerWebExchange
 import org.springframework.web.util.UriBuilder
-import java.time.Duration
-import java.time.Instant
-import java.time.ZoneOffset
+import reactor.core.publisher.Mono
 
 /**
  * Tests for [ServerOneTimeTokenLoginDsl]
@@ -102,7 +99,7 @@ class ServerOneTimeTokenLoginDslTests {
             .is3xxRedirection()
             .expectHeader().valueEquals("Location", "/login/ott")
 
-        val token = TestServerOneTimeTokenGenerationSuccessHandler.lastToken?.tokenValue
+        val token = lastToken()!!.tokenValue
 
         client.mutateWith(SecurityMockServerConfigurers.csrf())
             .post()
@@ -136,7 +133,7 @@ class ServerOneTimeTokenLoginDslTests {
             .is3xxRedirection()
             .expectHeader().valueEquals("Location", "/redirected")
 
-        val token = TestServerOneTimeTokenGenerationSuccessHandler.lastToken?.tokenValue
+        val token = lastToken()!!.tokenValue
 
         client.mutateWith(SecurityMockServerConfigurers.csrf())
             .post()
@@ -154,8 +151,8 @@ class ServerOneTimeTokenLoginDslTests {
     }
 
     @Test
-    fun `oneTimeToken when custom token expiration time set then authenticate`() {
-        spring.register(OneTimeTokenConfigWithCustomTokenExpirationTime::class.java).autowire()
+    fun `oneTimeToken when custom request resolver set then custom resolver use`() {
+        spring.register(OneTimeTokenConfigWithCustomRequestResolver::class.java).autowire()
 
         // @formatter:off
         client.mutateWith(SecurityMockServerConfigurers.csrf())
@@ -171,29 +168,18 @@ class ServerOneTimeTokenLoginDslTests {
                 .is3xxRedirection()
                 .expectHeader().valueEquals("Location", "/login/ott")
 
-        client.mutateWith(SecurityMockServerConfigurers.csrf())
-                .post()
-                .uri{ uriBuilder:UriBuilder -> uriBuilder
-                        .path("/ott/generate")
-                        .build()
-                }
-                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
-                .body(BodyInserters.fromFormData("username", "user"))
-                .exchange()
-                .expectStatus()
-                .is3xxRedirection()
-                .expectHeader().valueEquals("Location", "/login/ott")
-
-        val token = TestServerOneTimeTokenGenerationSuccessHandler.lastToken
+        val resolver = spring.context
+                .getBean(ServerGenerateOneTimeTokenRequestResolver::class.java)
 
-        Assertions.assertThat(getCurrentMinutes(token!!.expiresAt)).isEqualTo(10)
+        verify(resolver, Mockito.times(1))
+                .resolve(ArgumentMatchers.any(ServerWebExchange::class.java))
+        // @formatter:on
     }
 
-    private fun getCurrentMinutes(expiresAt:Instant): Int {
-        val expiresMinutes = expiresAt.atZone(ZoneOffset.UTC).minute
-        val currentMinutes = Instant.now().atZone(ZoneOffset.UTC).minute
-        return expiresMinutes - currentMinutes
-    }
+    private fun lastToken():OneTimeToken? =
+        spring.context.getBean(TestServerOneTimeTokenGenerationSuccessHandler::class.java)
+                .lastToken
+
 
     @Configuration
     @EnableWebFlux
@@ -202,18 +188,23 @@ class ServerOneTimeTokenLoginDslTests {
     open class OneTimeTokenConfig {
 
         @Bean
-        open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+        open fun springWebFilterChain(http: ServerHttpSecurity,
+                                      ottSuccessHandler: ServerOneTimeTokenGenerationSuccessHandler): SecurityWebFilterChain {
             // @formatter:off
             return http {
                 authorizeExchange {
                     authorize(anyExchange, authenticated)
                 }
                 oneTimeTokenLogin {
-                    tokenGenerationSuccessHandler = TestServerOneTimeTokenGenerationSuccessHandler()
+                    tokenGenerationSuccessHandler = ottSuccessHandler
                 }
             }
             // @formatter:on
         }
+
+        @Bean
+        open fun ottSuccessHandler(): ServerOneTimeTokenGenerationSuccessHandler =
+                TestServerOneTimeTokenGenerationSuccessHandler()
     }
 
     @Configuration
@@ -223,7 +214,8 @@ class ServerOneTimeTokenLoginDslTests {
     open class OneTimeTokenDifferentUrlsConfig {
 
         @Bean
-        open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+        open fun springWebFilterChain(http: ServerHttpSecurity,
+                                      ottSuccessHandler: ServerOneTimeTokenGenerationSuccessHandler): SecurityWebFilterChain {
             // @formatter:off
             return http {
                 authorizeExchange {
@@ -231,13 +223,17 @@ class ServerOneTimeTokenLoginDslTests {
                 }
                 oneTimeTokenLogin {
                     tokenGeneratingUrl = "/generateurl"
-                    tokenGenerationSuccessHandler = TestServerOneTimeTokenGenerationSuccessHandler("/redirected")
+                    tokenGenerationSuccessHandler = ottSuccessHandler
                     loginProcessingUrl = "/loginprocessingurl"
                     authenticationSuccessHandler = RedirectServerAuthenticationSuccessHandler("/authenticated")
                 }
             }
             // @formatter:on
         }
+
+        @Bean
+        open fun ottSuccessHandler(): ServerOneTimeTokenGenerationSuccessHandler =
+                TestServerOneTimeTokenGenerationSuccessHandler("/redirected")
     }
 
     @Configuration(proxyBeanMethods = false)
@@ -252,36 +248,33 @@ class ServerOneTimeTokenLoginDslTests {
     @EnableWebFlux
     @EnableWebFluxSecurity
     @Import(OneTimeTokenLoginSpecTests.UserDetailsServiceConfig::class)
-    open class OneTimeTokenConfigWithCustomTokenExpirationTime {
+    open class OneTimeTokenConfigWithCustomRequestResolver {
         @Bean
-        open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+        open fun securityWebFilterChain(http: ServerHttpSecurity,
+                                        ottSuccessHandler: ServerOneTimeTokenGenerationSuccessHandler): SecurityWebFilterChain {
             // @formatter:off
             return http {
                 authorizeExchange {
                     authorize(anyExchange, authenticated)
                 }
                 oneTimeTokenLogin {
-                    tokenGenerationSuccessHandler = TestServerOneTimeTokenGenerationSuccessHandler()
+                    tokenGenerationSuccessHandler = ottSuccessHandler
                 }
             }
         }
 
         @Bean
-        open fun resolver(): ServerGenerateOneTimeTokenRequestResolver {
-            val resolver = DefaultServerGenerateOneTimeTokenRequestResolver()
-            return ServerGenerateOneTimeTokenRequestResolver { exchange ->
-                resolver.resolve(exchange)
-                        .map { request -> GenerateOneTimeTokenRequest(request.username, Duration.ofSeconds(600)) }
-            }
-        }
+        open fun resolver(): ServerGenerateOneTimeTokenRequestResolver =
+                Mockito.spy(DefaultServerGenerateOneTimeTokenRequestResolver())
+
+        @Bean
+        open fun ottSuccessHandler(): ServerOneTimeTokenGenerationSuccessHandler =
+                TestServerOneTimeTokenGenerationSuccessHandler()
     }
 
     private class TestServerOneTimeTokenGenerationSuccessHandler: ServerOneTimeTokenGenerationSuccessHandler {
         private var delegate: ServerRedirectOneTimeTokenGenerationSuccessHandler? = null
-
-        companion object {
-            var lastToken: OneTimeToken? = null
-        }
+        var lastToken: OneTimeToken? = null
 
         constructor() {
             this.delegate = ServerRedirectOneTimeTokenGenerationSuccessHandler("/login/ott")