Skip to content

Commit 81e4c72

Browse files
Max Batischevmarcusdacoregio
Max Batischev
authored andcommitted
Add One-Time Token Login support to Kotlin DSL
Closes gh-15698
1 parent 3b2afd7 commit 81e4c72

File tree

4 files changed

+498
-48
lines changed

4 files changed

+498
-48
lines changed

config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt

Lines changed: 80 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.web
1818

1919
import jakarta.servlet.Filter
2020
import jakarta.servlet.http.HttpServletRequest
21-
import org.checkerframework.checker.units.qual.C
2221
import org.springframework.context.ApplicationContext
2322
import org.springframework.security.authentication.AuthenticationManager
2423
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
@@ -60,7 +59,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher
6059
* @param httpConfiguration the configurations to apply to [HttpSecurity]
6160
*/
6261
operator fun HttpSecurity.invoke(httpConfiguration: HttpSecurityDsl.() -> Unit) =
63-
HttpSecurityDsl(this, httpConfiguration).build()
62+
HttpSecurityDsl(this, httpConfiguration).build()
6463

6564
/**
6665
* An [HttpSecurity] Kotlin DSL created by [`http { }`][invoke]
@@ -104,7 +103,10 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
104103
* @param configurer
105104
* the [SecurityConfigurerAdapter] for further customizations
106105
*/
107-
fun <C : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> apply(configurer: C, configuration: C.() -> Unit = { }): C {
106+
fun <C : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> apply(
107+
configurer: C,
108+
configuration: C.() -> Unit = { }
109+
): C {
108110
return this.http.apply(configurer).apply(configuration)
109111
}
110112

@@ -134,7 +136,10 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
134136
* the [HttpSecurity] for further customizations
135137
* @since 6.2
136138
*/
137-
fun <C : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> with(configurer: C, configuration: C.() -> Unit = { }): HttpSecurity? {
139+
fun <C : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> with(
140+
configurer: C,
141+
configuration: C.() -> Unit = { }
142+
): HttpSecurity? {
138143
return this.http.with(configurer, configuration)
139144
}
140145

@@ -299,7 +304,8 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
299304
* @since 5.7
300305
*/
301306
fun authorizeHttpRequests(authorizeHttpRequestsConfiguration: AuthorizeHttpRequestsDsl.() -> Unit) {
302-
val authorizeHttpRequestsCustomizer = AuthorizeHttpRequestsDsl(this.context).apply(authorizeHttpRequestsConfiguration).get()
307+
val authorizeHttpRequestsCustomizer =
308+
AuthorizeHttpRequestsDsl(this.context).apply(authorizeHttpRequestsConfiguration).get()
303309
this.http.authorizeHttpRequests(authorizeHttpRequestsCustomizer)
304310
}
305311

@@ -772,42 +778,42 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
772778
this.http.saml2Logout(saml2LogoutCustomizer)
773779
}
774780

775-
/**
776-
* Configures a SAML 2.0 relying party metadata endpoint.
777-
*
778-
* A [RelyingPartyRegistrationRepository] is required and must be registered with
779-
* the [ApplicationContext] or configured via
780-
* [Saml2Dsl.relyingPartyRegistrationRepository]
781-
*
782-
* Example:
783-
*
784-
* The following example shows the minimal configuration required, using a
785-
* hypothetical asserting party.
786-
*
787-
* ```
788-
* @Configuration
789-
* @EnableWebSecurity
790-
* class SecurityConfig {
791-
*
792-
* @Bean
793-
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
794-
* http {
795-
* saml2Login { }
796-
* saml2Metadata { }
797-
* }
798-
* return http.build()
799-
* }
800-
* }
801-
* ```
802-
* @param saml2MetadataConfiguration custom configuration to configure the
803-
* SAML2 relying party metadata endpoint
804-
* @see [Saml2MetadataDsl]
805-
* @since 6.1
806-
*/
807-
fun saml2Metadata(saml2MetadataConfiguration: Saml2MetadataDsl.() -> Unit) {
808-
val saml2MetadataCustomizer = Saml2MetadataDsl().apply(saml2MetadataConfiguration).get()
809-
this.http.saml2Metadata(saml2MetadataCustomizer)
810-
}
781+
/**
782+
* Configures a SAML 2.0 relying party metadata endpoint.
783+
*
784+
* A [RelyingPartyRegistrationRepository] is required and must be registered with
785+
* the [ApplicationContext] or configured via
786+
* [Saml2Dsl.relyingPartyRegistrationRepository]
787+
*
788+
* Example:
789+
*
790+
* The following example shows the minimal configuration required, using a
791+
* hypothetical asserting party.
792+
*
793+
* ```
794+
* @Configuration
795+
* @EnableWebSecurity
796+
* class SecurityConfig {
797+
*
798+
* @Bean
799+
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
800+
* http {
801+
* saml2Login { }
802+
* saml2Metadata { }
803+
* }
804+
* return http.build()
805+
* }
806+
* }
807+
* ```
808+
* @param saml2MetadataConfiguration custom configuration to configure the
809+
* SAML2 relying party metadata endpoint
810+
* @see [Saml2MetadataDsl]
811+
* @since 6.1
812+
*/
813+
fun saml2Metadata(saml2MetadataConfiguration: Saml2MetadataDsl.() -> Unit) {
814+
val saml2MetadataCustomizer = Saml2MetadataDsl().apply(saml2MetadataConfiguration).get()
815+
this.http.saml2Metadata(saml2MetadataCustomizer)
816+
}
811817

812818
/**
813819
* Allows configuring how an anonymous user is represented.
@@ -965,6 +971,36 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
965971
this.http.oidcLogout(oidcLogoutCustomizer)
966972
}
967973

974+
/**
975+
* Configures One-Time Token Login Support.
976+
*
977+
* Example:
978+
*
979+
* ```
980+
* @Configuration
981+
* @EnableWebSecurity
982+
* class SecurityConfig {
983+
*
984+
* @Bean
985+
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
986+
* http {
987+
* oneTimeTokenLogin {
988+
* generatedOneTimeTokenHandler = MyMagicLinkGeneratedOneTimeTokenHandler()
989+
* }
990+
* }
991+
* return http.build()
992+
* }
993+
* }
994+
*
995+
* ```
996+
* @since 6.4
997+
* @param oneTimeTokenLoginConfiguration custom configuration to configure one-time token login
998+
*/
999+
fun oneTimeTokenLogin(oneTimeTokenLoginConfiguration: OneTimeTokenLoginDsl.() -> Unit) {
1000+
val oneTimeTokenLoginCustomizer = OneTimeTokenLoginDsl().apply(oneTimeTokenLoginConfiguration).get()
1001+
this.http.oneTimeTokenLogin(oneTimeTokenLoginCustomizer)
1002+
}
1003+
9681004
/**
9691005
* Configures Remember Me authentication.
9701006
*
@@ -1050,7 +1086,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
10501086
* (i.e. known) with Spring Security.
10511087
*/
10521088
@Suppress("DEPRECATION")
1053-
inline fun <reified T: Filter> addFilterAt(filter: Filter) {
1089+
inline fun <reified T : Filter> addFilterAt(filter: Filter) {
10541090
this.addFilterAt(filter, T::class.java)
10551091
}
10561092

@@ -1109,7 +1145,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
11091145
* (i.e. known) with Spring Security.
11101146
*/
11111147
@Suppress("DEPRECATION")
1112-
inline fun <reified T: Filter> addFilterAfter(filter: Filter) {
1148+
inline fun <reified T : Filter> addFilterAfter(filter: Filter) {
11131149
this.addFilterAfter(filter, T::class.java)
11141150
}
11151151

@@ -1168,7 +1204,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
11681204
* (i.e. known) with Spring Security.
11691205
*/
11701206
@Suppress("DEPRECATION")
1171-
inline fun <reified T: Filter> addFilterBefore(filter: Filter) {
1207+
inline fun <reified T : Filter> addFilterBefore(filter: Filter) {
11721208
this.addFilterBefore(filter, T::class.java)
11731209
}
11741210

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.web
18+
19+
import org.springframework.security.authentication.AuthenticationProvider
20+
import org.springframework.security.authentication.ott.OneTimeTokenService
21+
import org.springframework.security.config.annotation.web.builders.HttpSecurity
22+
import org.springframework.security.config.annotation.web.configurers.ott.OneTimeTokenLoginConfigurer
23+
import org.springframework.security.web.authentication.AuthenticationConverter
24+
import org.springframework.security.web.authentication.AuthenticationFailureHandler
25+
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
26+
import org.springframework.security.web.authentication.ott.GeneratedOneTimeTokenHandler
27+
28+
/**
29+
* A Kotlin DSL to configure [HttpSecurity] OAuth 2.0 login using idiomatic Kotlin code.
30+
*
31+
* @author Max Batischev
32+
* @since 6.4
33+
* @property oneTimeTokenService configures the [OneTimeTokenService] used to generate and consume
34+
* @property authenticationConverter Use this [AuthenticationConverter] when converting incoming requests to an authentication
35+
* @property authenticationFailureHandler the [AuthenticationFailureHandler] to use when authentication
36+
* @property authenticationSuccessHandler the [AuthenticationSuccessHandler] to be used
37+
* @property defaultSubmitPageUrl sets the URL that the default submit page will be generated
38+
* @property showDefaultSubmitPage configures whether the default one-time token submit page should be shown
39+
* @property loginProcessingUrl the URL to process the login request
40+
* @property generateTokenUrl the URL that a One-Time Token generate request will be processed
41+
* @property generatedOneTimeTokenHandler the strategy to be used to handle generated one-time tokens
42+
* @property authenticationProvider the [AuthenticationProvider] to use when authenticating the user
43+
*/
44+
@SecurityMarker
45+
class OneTimeTokenLoginDsl {
46+
var oneTimeTokenService: OneTimeTokenService? = null
47+
var authenticationConverter: AuthenticationConverter? = null
48+
var authenticationFailureHandler: AuthenticationFailureHandler? = null
49+
var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
50+
var defaultSubmitPageUrl: String? = null
51+
var loginProcessingUrl: String? = null
52+
var generateTokenUrl: String? = null
53+
var showDefaultSubmitPage: Boolean? = true
54+
var generatedOneTimeTokenHandler: GeneratedOneTimeTokenHandler? = null
55+
var authenticationProvider: AuthenticationProvider? = null
56+
57+
internal fun get(): (OneTimeTokenLoginConfigurer<HttpSecurity>) -> Unit {
58+
return { oneTimeTokenLoginConfigurer ->
59+
oneTimeTokenService?.also { oneTimeTokenLoginConfigurer.oneTimeTokenService(oneTimeTokenService) }
60+
authenticationConverter?.also { oneTimeTokenLoginConfigurer.authenticationConverter(authenticationConverter) }
61+
authenticationFailureHandler?.also {
62+
oneTimeTokenLoginConfigurer.authenticationFailureHandler(
63+
authenticationFailureHandler
64+
)
65+
}
66+
authenticationSuccessHandler?.also {
67+
oneTimeTokenLoginConfigurer.authenticationSuccessHandler(
68+
authenticationSuccessHandler
69+
)
70+
}
71+
defaultSubmitPageUrl?.also { oneTimeTokenLoginConfigurer.defaultSubmitPageUrl(defaultSubmitPageUrl) }
72+
showDefaultSubmitPage?.also { oneTimeTokenLoginConfigurer.showDefaultSubmitPage(showDefaultSubmitPage!!) }
73+
loginProcessingUrl?.also { oneTimeTokenLoginConfigurer.loginProcessingUrl(loginProcessingUrl) }
74+
generateTokenUrl?.also { oneTimeTokenLoginConfigurer.generateTokenUrl(generateTokenUrl) }
75+
generatedOneTimeTokenHandler?.also {
76+
oneTimeTokenLoginConfigurer.generatedOneTimeTokenHandler(
77+
generatedOneTimeTokenHandler
78+
)
79+
}
80+
authenticationProvider?.also { oneTimeTokenLoginConfigurer.authenticationProvider(authenticationProvider) }
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)