From c929bda7abeb51765adbfd451de44dddf25cddb3 Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Mon, 1 Jun 2026 14:18:36 +0300 Subject: [PATCH] Add missing loginPage property to One-Time Token DSLs This adds loginPage support to both servlet and reactive One-Time Token Kotlin DSLs. Closes: gh-19249 Signed-off-by: Andrey Litvitski --- .../annotation/web/OneTimeTokenLoginDsl.kt | 5 +++ .../web/server/ServerOneTimeTokenLoginDsl.kt | 5 +++ .../web/OneTimeTokenLoginDslTests.kt | 39 ++++++++++++++++++ .../server/ServerOneTimeTokenLoginDslTests.kt | 40 +++++++++++++++++++ 4 files changed, 89 insertions(+) diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDsl.kt index e1508f54b54..867f191a8a7 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDsl.kt @@ -30,6 +30,7 @@ import org.springframework.security.web.authentication.ott.OneTimeTokenGeneratio * A Kotlin DSL to configure [HttpSecurity] OAuth 2.0 login using idiomatic Kotlin code. * * @author Max Batischev + * @author Andrey Litvitski * @since 6.4 * @property tokenService configures the [OneTimeTokenService] used to generate and consume * @property authenticationConverter Use this [AuthenticationConverter] when converting incoming requests to an authentication @@ -39,6 +40,8 @@ import org.springframework.security.web.authentication.ott.OneTimeTokenGeneratio * @property defaultSubmitPageUrl sets the URL that the default submit page will be generated * @property showDefaultSubmitPage configures whether the default one-time token submit page should be shown * @property loginProcessingUrl the URL to process the login request + * @property loginPage the login page to redirect to if authentication is required (i.e. + * "/login") * @property tokenGeneratingUrl the URL that a One-Time Token generate request will be processed * @property oneTimeTokenGenerationSuccessHandler the strategy to be used to handle generated one-time tokens * @property authenticationProvider the [AuthenticationProvider] to use when authenticating the user @@ -52,6 +55,7 @@ class OneTimeTokenLoginDsl { var generateRequestResolver: GenerateOneTimeTokenRequestResolver? = null var defaultSubmitPageUrl: String? = null var loginProcessingUrl: String? = null + var loginPage: String? = null var tokenGeneratingUrl: String? = null var showDefaultSubmitPage: Boolean? = true var oneTimeTokenGenerationSuccessHandler: OneTimeTokenGenerationSuccessHandler? = null @@ -79,6 +83,7 @@ class OneTimeTokenLoginDsl { defaultSubmitPageUrl?.also { oneTimeTokenLoginConfigurer.defaultSubmitPageUrl(defaultSubmitPageUrl) } showDefaultSubmitPage?.also { oneTimeTokenLoginConfigurer.showDefaultSubmitPage(showDefaultSubmitPage!!) } loginProcessingUrl?.also { oneTimeTokenLoginConfigurer.loginProcessingUrl(loginProcessingUrl) } + loginPage?.also { oneTimeTokenLoginConfigurer.loginPage((loginPage)) } tokenGeneratingUrl?.also { oneTimeTokenLoginConfigurer.tokenGeneratingUrl(tokenGeneratingUrl) } oneTimeTokenGenerationSuccessHandler?.also { oneTimeTokenLoginConfigurer.tokenGenerationSuccessHandler( diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOneTimeTokenLoginDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOneTimeTokenLoginDsl.kt index b4a16f3677f..ebc5fed6dac 100644 --- a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOneTimeTokenLoginDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOneTimeTokenLoginDsl.kt @@ -29,6 +29,7 @@ import org.springframework.security.web.server.context.ServerSecurityContextRepo * A Kotlin DSL to configure [ServerHttpSecurity] form login using idiomatic Kotlin code. * * @author Max Batischev + * @author Andrey Litvitski * @since 6.4 * @property tokenService configures the [ReactiveOneTimeTokenService] used to generate and consume * @property authenticationManager configures the [ReactiveAuthenticationManager] used to generate and consume @@ -39,6 +40,8 @@ import org.springframework.security.web.server.context.ServerSecurityContextRepo * @property defaultSubmitPageUrl sets the URL that the default submit page will be generated * @property showDefaultSubmitPage configures whether the default one-time token submit page should be shown * @property loginProcessingUrl the URL to process the login request + * @property loginPage the login page to redirect to if authentication is required (i.e. + * "/login") * @property tokenGeneratingUrl the URL that a One-Time Token generate request will be processed * @property tokenGenerationSuccessHandler the strategy to be used to handle generated one-time tokens * @property securityContextRepository the [ServerSecurityContextRepository] used to save the [Authentication]. For the [SecurityContext] to be loaded on subsequent requests the [ReactorContextWebFilter] must be configured to be able to load the value (they are not implicitly linked). @@ -55,6 +58,7 @@ class ServerOneTimeTokenLoginDsl { var generateRequestResolver: ServerGenerateOneTimeTokenRequestResolver? = null var defaultSubmitPageUrl: String? = null var loginProcessingUrl: String? = null + var loginPage: String? = null var tokenGeneratingUrl: String? = null var showDefaultSubmitPage: Boolean? = true @@ -78,6 +82,7 @@ class ServerOneTimeTokenLoginDsl { defaultSubmitPageUrl?.also { oneTimeTokenLogin.defaultSubmitPageUrl(defaultSubmitPageUrl) } showDefaultSubmitPage?.also { oneTimeTokenLogin.showDefaultSubmitPage(showDefaultSubmitPage!!) } loginProcessingUrl?.also { oneTimeTokenLogin.loginProcessingUrl(loginProcessingUrl) } + loginPage?.also { oneTimeTokenLogin.loginPage((loginPage)) } tokenGeneratingUrl?.also { oneTimeTokenLogin.tokenGeneratingUrl(tokenGeneratingUrl) } tokenGenerationSuccessHandler?.also { oneTimeTokenLogin.tokenGenerationSuccessHandler( diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDslTests.kt index 4cbe7c0d541..103d04fb361 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/OneTimeTokenLoginDslTests.kt @@ -56,6 +56,7 @@ import java.time.Instant * Tests for [OneTimeTokenLoginDsl] * * @author Max Batischev + * @author Andrey Litvitski */ @ExtendWith(SpringTestContextExtension::class) class OneTimeTokenLoginDslTests { @@ -142,6 +143,17 @@ class OneTimeTokenLoginDslTests { } + @Test + fun `request when secure and custom login page then redirects to custom login page`() { + spring.register(LoginPageConfig::class.java).autowire() + + mockMvc.perform(MockMvcRequestBuilders.get("/")) + .andExpectAll( + MockMvcResultMatchers.status().isFound(), + MockMvcResultMatchers.redirectedUrl("/log-in") + ) + } + private fun getLastToken(): OneTimeToken { val lastToken = spring.context .getBean(TestOneTimeTokenGenerationSuccessHandler::class.java).lastToken @@ -244,6 +256,33 @@ class OneTimeTokenLoginDslTests { } } + @EnableWebSecurity + @Configuration(proxyBeanMethods = false) + @Import(UserDetailsServiceConfig::class) + open class LoginPageConfig { + + @Bean + open fun securityFilterChain( + http: HttpSecurity, + ottSuccessHandler: OneTimeTokenGenerationSuccessHandler + ): SecurityFilterChain { + http { + authorizeHttpRequests { + authorize(anyRequest, authenticated) + } + oneTimeTokenLogin { + loginPage = "/log-in" + oneTimeTokenGenerationSuccessHandler = ottSuccessHandler + } + } + return http.build() + } + + @Bean + open fun ottSuccessHandler() = + TestOneTimeTokenGenerationSuccessHandler() + } + @Configuration(proxyBeanMethods = false) open class UserDetailsServiceConfig { diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOneTimeTokenLoginDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOneTimeTokenLoginDslTests.kt index 5e0f568e758..853ea8781fe 100644 --- a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOneTimeTokenLoginDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOneTimeTokenLoginDslTests.kt @@ -52,6 +52,7 @@ import reactor.core.publisher.Mono * Tests for [ServerOneTimeTokenLoginDsl] * * @author Max Batischev + * @author Andrey Litvitski */ @ExtendWith(SpringTestContextExtension::class) class ServerOneTimeTokenLoginDslTests { @@ -176,10 +177,49 @@ class ServerOneTimeTokenLoginDslTests { // @formatter:on } + @Test + fun `request when secure and custom login page then redirects to custom login page`() { + spring.register(LoginPageConfig::class.java).autowire() + + client.get() + .uri("/") + .exchange() + .expectStatus().is3xxRedirection + .expectHeader().valueEquals("Location", "/log-in") + } + private fun lastToken():OneTimeToken? = spring.context.getBean(TestServerOneTimeTokenGenerationSuccessHandler::class.java) .lastToken + @Configuration + @EnableWebFlux + @EnableWebFluxSecurity + @Import(UserDetailsServiceConfig::class) + open class LoginPageConfig { + + @Bean + open fun springWebFilterChain( + http: ServerHttpSecurity, + ottSuccessHandler: ServerOneTimeTokenGenerationSuccessHandler + ): SecurityWebFilterChain { + // @formatter:off + return http { + authorizeExchange { + authorize(anyExchange, authenticated) + } + oneTimeTokenLogin { + loginPage = "/log-in" + tokenGenerationSuccessHandler = ottSuccessHandler + } + } + // @formatter:on + } + + @Bean + open fun ottSuccessHandler(): ServerOneTimeTokenGenerationSuccessHandler = + TestServerOneTimeTokenGenerationSuccessHandler() + } @Configuration @EnableWebFlux