浏览代码

OTT Tests use Mocks Instead of Comparing Expires

Previously, expires was compared to test if a custom implementations
were used. Now the tests verify this through mocks.

Closes gh-16515
Rob Winch 6 月之前
父节点
当前提交
10394c8f2a

+ 43 - 27
config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java

@@ -19,7 +19,6 @@ package org.springframework.security.config.annotation.web.configurers.ott;
 import java.io.IOException;
 import java.time.Duration;
 import java.time.Instant;
-import java.time.ZoneOffset;
 
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
@@ -32,8 +31,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
+import org.springframework.security.authentication.ott.DefaultOneTimeToken;
 import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
 import org.springframework.security.authentication.ott.OneTimeToken;
+import org.springframework.security.authentication.ott.OneTimeTokenService;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -44,7 +45,6 @@ import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.provisioning.InMemoryUserDetailsManager;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
-import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver;
 import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver;
 import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
 import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
@@ -55,6 +55,11 @@ import org.springframework.test.web.servlet.MockMvc;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
 import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
 import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
@@ -72,6 +77,15 @@ public class OneTimeTokenLoginConfigurerTests {
 	@Autowired(required = false)
 	MockMvc mvc;
 
+	@Autowired(required = false)
+	private GenerateOneTimeTokenRequestResolver resolver;
+
+	@Autowired(required = false)
+	private OneTimeTokenService tokenService;
+
+	@Autowired(required = false)
+	private OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler;
+
 	@Test
 	void oneTimeTokenWhenCorrectTokenThenCanAuthenticate() throws Exception {
 		this.spring.register(OneTimeTokenDefaultConfig.class).autowire();
@@ -202,21 +216,18 @@ public class OneTimeTokenLoginConfigurerTests {
 
 	@Test
 	void oneTimeTokenWhenCustomTokenExpirationTimeSetThenAuthenticate() throws Exception {
-		this.spring.register(OneTimeTokenConfigWithCustomTokenExpirationTime.class).autowire();
-		this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf()))
-			.andExpectAll(status().isFound(), redirectedUrl("/login/ott"));
-
-		OneTimeToken token = getLastToken();
-
-		this.mvc.perform(post("/login/ott").param("token", token.getTokenValue()).with(csrf()))
-			.andExpectAll(status().isFound(), redirectedUrl("/"), authenticated());
-		assertThat(getCurrentMinutes(token.getExpiresAt())).isEqualTo(10);
-	}
-
-	private int getCurrentMinutes(Instant expiresAt) {
-		int expiresMinutes = expiresAt.atZone(ZoneOffset.UTC).getMinute();
-		int currentMinutes = Instant.now().atZone(ZoneOffset.UTC).getMinute();
-		return expiresMinutes - currentMinutes;
+		this.spring.register(OneTimeTokenConfigWithCustomImpls.class).autowire();
+		GenerateOneTimeTokenRequest expectedGenerateRequest = new GenerateOneTimeTokenRequest("username-123",
+				Duration.ofMinutes(10));
+		OneTimeToken ott = new DefaultOneTimeToken("token-123", expectedGenerateRequest.getUsername(),
+				Instant.now().plus(expectedGenerateRequest.getExpiresIn()));
+		given(this.resolver.resolve(any())).willReturn(expectedGenerateRequest);
+		given(this.tokenService.generate(expectedGenerateRequest)).willReturn(ott);
+		this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf()));
+
+		verify(this.resolver).resolve(any());
+		verify(this.tokenService).generate(expectedGenerateRequest);
+		verify(this.tokenGenerationSuccessHandler).handle(any(), any(), eq(ott));
 	}
 
 	private OneTimeToken getLastToken() {
@@ -228,17 +239,21 @@ public class OneTimeTokenLoginConfigurerTests {
 	@Configuration(proxyBeanMethods = false)
 	@EnableWebSecurity
 	@Import(UserDetailsServiceConfig.class)
-	static class OneTimeTokenConfigWithCustomTokenExpirationTime {
+	static class OneTimeTokenConfigWithCustomImpls {
 
 		@Bean
 		SecurityFilterChain securityFilterChain(HttpSecurity http,
+				GenerateOneTimeTokenRequestResolver ottRequestResolver, OneTimeTokenService ottTokenService,
 				OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception {
+
 			// @formatter:off
-			http
+				http
 					.authorizeHttpRequests((authz) -> authz
 							.anyRequest().authenticated()
 					)
 					.oneTimeTokenLogin((ott) -> ott
+							.generateRequestResolver(ottRequestResolver)
+							.tokenService(ottTokenService)
 							.tokenGenerationSuccessHandler(ottSuccessHandler)
 					);
 			// @formatter:on
@@ -246,17 +261,18 @@ public class OneTimeTokenLoginConfigurerTests {
 		}
 
 		@Bean
-		TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
-			return new TestOneTimeTokenGenerationSuccessHandler();
+		GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() {
+			return mock(GenerateOneTimeTokenRequestResolver.class);
 		}
 
 		@Bean
-		GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() {
-			DefaultGenerateOneTimeTokenRequestResolver delegate = new DefaultGenerateOneTimeTokenRequestResolver();
-			return (request) -> {
-				GenerateOneTimeTokenRequest generate = delegate.resolve(request);
-				return new GenerateOneTimeTokenRequest(generate.getUsername(), Duration.ofSeconds(600));
-			};
+		OneTimeTokenService ottService() {
+			return mock(OneTimeTokenService.class);
+		}
+
+		@Bean
+		OneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
+			return mock(OneTimeTokenGenerationSuccessHandler.class);
 		}
 
 	}

+ 46 - 24
config/src/test/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDslTests.kt

@@ -16,6 +16,10 @@
 
 package org.springframework.security.config.annotation.web
 
+import io.mockk.every
+import io.mockk.justRun
+import io.mockk.mockk
+import io.mockk.verify
 import jakarta.servlet.http.HttpServletRequest
 import jakarta.servlet.http.HttpServletResponse
 import org.assertj.core.api.Assertions.assertThat
@@ -25,7 +29,10 @@ import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
 import org.springframework.context.annotation.Import
+import org.springframework.security.authentication.ott.DefaultOneTimeToken
+import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest
 import org.springframework.security.authentication.ott.OneTimeToken
+import org.springframework.security.authentication.ott.OneTimeTokenService
 import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
 import org.springframework.security.config.test.SpringTestContext
@@ -38,6 +45,7 @@ import org.springframework.security.test.web.servlet.response.SecurityMockMvcRes
 import org.springframework.security.web.SecurityFilterChain
 import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
 import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver
+import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver
 import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
 import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
 import org.springframework.test.web.servlet.MockMvc
@@ -60,6 +68,15 @@ class OneTimeTokenLoginDslTests {
     @Autowired
     private lateinit var mockMvc: MockMvc
 
+    @Autowired(required = false)
+    private lateinit var resolver: GenerateOneTimeTokenRequestResolver
+
+    @Autowired(required = false)
+    private lateinit var tokenService: OneTimeTokenService
+
+    @Autowired(required = false)
+    private lateinit var tokenGenerationSuccessHandler: OneTimeTokenGenerationSuccessHandler
+
     @Test
     fun `oneTimeToken when correct token then can authenticate`() {
         spring.register(OneTimeTokenConfig::class.java).autowire()
@@ -110,29 +127,22 @@ class OneTimeTokenLoginDslTests {
     }
 
     @Test
-    fun `oneTimeToken when custom resolver set then use custom token`() {
-        spring.register(OneTimeTokenConfigWithCustomTokenResolver::class.java).autowire()
-
+    fun `oneTimeToken when custom impls set then used`() {
+        spring.register(OneTimeTokenConfigWithCustomImpls::class.java).autowire()
+        val expectedGenerateRequest = GenerateOneTimeTokenRequest("username-123", Duration.ofMinutes(10));
+        val ott = DefaultOneTimeToken("token-123", expectedGenerateRequest.username, Instant.now().plus(expectedGenerateRequest.expiresIn))
+        every { resolver.resolve(any()) } returns expectedGenerateRequest
+        every { tokenService.generate(expectedGenerateRequest) } returns ott
+        justRun { tokenGenerationSuccessHandler.handle(any(), any(), eq(ott)) }
         this.mockMvc.perform(
                 MockMvcRequestBuilders.post("/ott/generate").param("username", "user")
                         .with(SecurityMockMvcRequestPostProcessors.csrf())
-        ).andExpectAll(
-                MockMvcResultMatchers
-                        .status()
-                        .isFound(),
-                MockMvcResultMatchers
-                        .redirectedUrl("/login/ott")
         )
 
-        val token = getLastToken()
-
-        assertThat(getCurrentMinutes(token!!.expiresAt)).isEqualTo(10)
-    }
+        verify { resolver.resolve(any()) }
+        verify { tokenService.generate(expectedGenerateRequest) }
+        verify { tokenGenerationSuccessHandler.handle(any(), any(), eq(ott)) }
 
-    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 getLastToken(): OneTimeToken {
@@ -170,20 +180,22 @@ class OneTimeTokenLoginDslTests {
     @Configuration
     @EnableWebSecurity
     @Import(UserDetailsServiceConfig::class)
-    open class OneTimeTokenConfigWithCustomTokenResolver {
+    open class OneTimeTokenConfigWithCustomImpls {
 
         @Bean
-        open fun securityFilterChain(http: HttpSecurity, ottSuccessHandler: OneTimeTokenGenerationSuccessHandler): SecurityFilterChain {
+        open fun securityFilterChain(http: HttpSecurity,
+                                     ottRequestResolver: GenerateOneTimeTokenRequestResolver,
+                                     ottService: OneTimeTokenService,
+                                     ottSuccessHandler: OneTimeTokenGenerationSuccessHandler): SecurityFilterChain {
             // @formatter:off
             http {
                 authorizeHttpRequests {
                     authorize(anyRequest, authenticated)
                 }
                 oneTimeTokenLogin {
+                    generateRequestResolver = ottRequestResolver
+                    tokenService = ottService
                     oneTimeTokenGenerationSuccessHandler = ottSuccessHandler
-                    generateRequestResolver = DefaultGenerateOneTimeTokenRequestResolver().apply {
-                        this.setExpiresIn(Duration.ofMinutes(10))
-                    }
                 }
             }
             // @formatter:on
@@ -191,8 +203,18 @@ class OneTimeTokenLoginDslTests {
         }
 
         @Bean
-        open fun ottSuccessHandler(): TestOneTimeTokenGenerationSuccessHandler {
-            return TestOneTimeTokenGenerationSuccessHandler()
+        open fun ottRequestResolver(): GenerateOneTimeTokenRequestResolver {
+            return mockk()
+        }
+
+        @Bean
+        open fun ottService(): OneTimeTokenService {
+            return mockk()
+        }
+
+        @Bean
+        open fun ottSuccessHandler(): OneTimeTokenGenerationSuccessHandler {
+            return mockk()
         }
 
     }