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
This commit is contained in:
+42
-26
@@ -19,7 +19,6 @@ package org.springframework.security.config.annotation.web.configurers.ott;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneOffset;
|
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
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.GenerateOneTimeTokenRequest;
|
||||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
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.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
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.provisioning.InMemoryUserDetailsManager;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
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.GenerateOneTimeTokenRequestResolver;
|
||||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
|
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.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatException;
|
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.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
|
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
|
||||||
@@ -72,6 +77,15 @@ public class OneTimeTokenLoginConfigurerTests {
|
|||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
MockMvc mvc;
|
MockMvc mvc;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private GenerateOneTimeTokenRequestResolver resolver;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private OneTimeTokenService tokenService;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void oneTimeTokenWhenCorrectTokenThenCanAuthenticate() throws Exception {
|
void oneTimeTokenWhenCorrectTokenThenCanAuthenticate() throws Exception {
|
||||||
this.spring.register(OneTimeTokenDefaultConfig.class).autowire();
|
this.spring.register(OneTimeTokenDefaultConfig.class).autowire();
|
||||||
@@ -202,21 +216,18 @@ public class OneTimeTokenLoginConfigurerTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void oneTimeTokenWhenCustomTokenExpirationTimeSetThenAuthenticate() throws Exception {
|
void oneTimeTokenWhenCustomTokenExpirationTimeSetThenAuthenticate() throws Exception {
|
||||||
this.spring.register(OneTimeTokenConfigWithCustomTokenExpirationTime.class).autowire();
|
this.spring.register(OneTimeTokenConfigWithCustomImpls.class).autowire();
|
||||||
this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf()))
|
GenerateOneTimeTokenRequest expectedGenerateRequest = new GenerateOneTimeTokenRequest("username-123",
|
||||||
.andExpectAll(status().isFound(), redirectedUrl("/login/ott"));
|
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()));
|
||||||
|
|
||||||
OneTimeToken token = getLastToken();
|
verify(this.resolver).resolve(any());
|
||||||
|
verify(this.tokenService).generate(expectedGenerateRequest);
|
||||||
this.mvc.perform(post("/login/ott").param("token", token.getTokenValue()).with(csrf()))
|
verify(this.tokenGenerationSuccessHandler).handle(any(), any(), eq(ott));
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private OneTimeToken getLastToken() {
|
private OneTimeToken getLastToken() {
|
||||||
@@ -228,17 +239,21 @@ public class OneTimeTokenLoginConfigurerTests {
|
|||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@Import(UserDetailsServiceConfig.class)
|
@Import(UserDetailsServiceConfig.class)
|
||||||
static class OneTimeTokenConfigWithCustomTokenExpirationTime {
|
static class OneTimeTokenConfigWithCustomImpls {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
SecurityFilterChain securityFilterChain(HttpSecurity http,
|
SecurityFilterChain securityFilterChain(HttpSecurity http,
|
||||||
|
GenerateOneTimeTokenRequestResolver ottRequestResolver, OneTimeTokenService ottTokenService,
|
||||||
OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception {
|
OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception {
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests((authz) -> authz
|
.authorizeHttpRequests((authz) -> authz
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.oneTimeTokenLogin((ott) -> ott
|
.oneTimeTokenLogin((ott) -> ott
|
||||||
|
.generateRequestResolver(ottRequestResolver)
|
||||||
|
.tokenService(ottTokenService)
|
||||||
.tokenGenerationSuccessHandler(ottSuccessHandler)
|
.tokenGenerationSuccessHandler(ottSuccessHandler)
|
||||||
);
|
);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
@@ -246,17 +261,18 @@ public class OneTimeTokenLoginConfigurerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
|
GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() {
|
||||||
return new TestOneTimeTokenGenerationSuccessHandler();
|
return mock(GenerateOneTimeTokenRequestResolver.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() {
|
OneTimeTokenService ottService() {
|
||||||
DefaultGenerateOneTimeTokenRequestResolver delegate = new DefaultGenerateOneTimeTokenRequestResolver();
|
return mock(OneTimeTokenService.class);
|
||||||
return (request) -> {
|
}
|
||||||
GenerateOneTimeTokenRequest generate = delegate.resolve(request);
|
|
||||||
return new GenerateOneTimeTokenRequest(generate.getUsername(), Duration.ofSeconds(600));
|
@Bean
|
||||||
};
|
OneTimeTokenGenerationSuccessHandler ottSuccessHandler() {
|
||||||
|
return mock(OneTimeTokenGenerationSuccessHandler.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-24
@@ -16,6 +16,10 @@
|
|||||||
|
|
||||||
package org.springframework.security.config.annotation.web
|
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.HttpServletRequest
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
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.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.context.annotation.Import
|
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.OneTimeToken
|
||||||
|
import org.springframework.security.authentication.ott.OneTimeTokenService
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
import org.springframework.security.config.test.SpringTestContext
|
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.SecurityFilterChain
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
|
||||||
import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver
|
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.OneTimeTokenGenerationSuccessHandler
|
||||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
|
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
@@ -60,6 +68,15 @@ class OneTimeTokenLoginDslTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private lateinit var mockMvc: MockMvc
|
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
|
@Test
|
||||||
fun `oneTimeToken when correct token then can authenticate`() {
|
fun `oneTimeToken when correct token then can authenticate`() {
|
||||||
spring.register(OneTimeTokenConfig::class.java).autowire()
|
spring.register(OneTimeTokenConfig::class.java).autowire()
|
||||||
@@ -110,29 +127,22 @@ class OneTimeTokenLoginDslTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `oneTimeToken when custom resolver set then use custom token`() {
|
fun `oneTimeToken when custom impls set then used`() {
|
||||||
spring.register(OneTimeTokenConfigWithCustomTokenResolver::class.java).autowire()
|
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(
|
this.mockMvc.perform(
|
||||||
MockMvcRequestBuilders.post("/ott/generate").param("username", "user")
|
MockMvcRequestBuilders.post("/ott/generate").param("username", "user")
|
||||||
.with(SecurityMockMvcRequestPostProcessors.csrf())
|
.with(SecurityMockMvcRequestPostProcessors.csrf())
|
||||||
).andExpectAll(
|
|
||||||
MockMvcResultMatchers
|
|
||||||
.status()
|
|
||||||
.isFound(),
|
|
||||||
MockMvcResultMatchers
|
|
||||||
.redirectedUrl("/login/ott")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val token = getLastToken()
|
verify { resolver.resolve(any()) }
|
||||||
|
verify { tokenService.generate(expectedGenerateRequest) }
|
||||||
|
verify { tokenGenerationSuccessHandler.handle(any(), any(), eq(ott)) }
|
||||||
|
|
||||||
assertThat(getCurrentMinutes(token!!.expiresAt)).isEqualTo(10)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
private fun getLastToken(): OneTimeToken {
|
||||||
@@ -170,20 +180,22 @@ class OneTimeTokenLoginDslTests {
|
|||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@Import(UserDetailsServiceConfig::class)
|
@Import(UserDetailsServiceConfig::class)
|
||||||
open class OneTimeTokenConfigWithCustomTokenResolver {
|
open class OneTimeTokenConfigWithCustomImpls {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
open fun securityFilterChain(http: HttpSecurity, ottSuccessHandler: OneTimeTokenGenerationSuccessHandler): SecurityFilterChain {
|
open fun securityFilterChain(http: HttpSecurity,
|
||||||
|
ottRequestResolver: GenerateOneTimeTokenRequestResolver,
|
||||||
|
ottService: OneTimeTokenService,
|
||||||
|
ottSuccessHandler: OneTimeTokenGenerationSuccessHandler): SecurityFilterChain {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
http {
|
http {
|
||||||
authorizeHttpRequests {
|
authorizeHttpRequests {
|
||||||
authorize(anyRequest, authenticated)
|
authorize(anyRequest, authenticated)
|
||||||
}
|
}
|
||||||
oneTimeTokenLogin {
|
oneTimeTokenLogin {
|
||||||
|
generateRequestResolver = ottRequestResolver
|
||||||
|
tokenService = ottService
|
||||||
oneTimeTokenGenerationSuccessHandler = ottSuccessHandler
|
oneTimeTokenGenerationSuccessHandler = ottSuccessHandler
|
||||||
generateRequestResolver = DefaultGenerateOneTimeTokenRequestResolver().apply {
|
|
||||||
this.setExpiresIn(Duration.ofMinutes(10))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
@@ -191,8 +203,18 @@ class OneTimeTokenLoginDslTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
open fun ottSuccessHandler(): TestOneTimeTokenGenerationSuccessHandler {
|
open fun ottRequestResolver(): GenerateOneTimeTokenRequestResolver {
|
||||||
return TestOneTimeTokenGenerationSuccessHandler()
|
return mockk()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun ottService(): OneTimeTokenService {
|
||||||
|
return mockk()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun ottSuccessHandler(): OneTimeTokenGenerationSuccessHandler {
|
||||||
|
return mockk()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user