Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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).
Expand All @@ -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

Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import java.time.Instant
* Tests for [OneTimeTokenLoginDsl]
*
* @author Max Batischev
* @author Andrey Litvitski
*/
@ExtendWith(SpringTestContextExtension::class)
class OneTimeTokenLoginDslTests {
Expand Down Expand Up @@ -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()
Comment on lines +146 to +148

@therepanic therepanic Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests for both versions were partially copied from FormLoginDslTests


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
Expand Down Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import reactor.core.publisher.Mono
* Tests for [ServerOneTimeTokenLoginDsl]
*
* @author Max Batischev
* @author Andrey Litvitski
*/
@ExtendWith(SpringTestContextExtension::class)
class ServerOneTimeTokenLoginDslTests {
Expand Down Expand Up @@ -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
Expand Down
Loading