mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-08 21:22:18 +00:00
Add One-Time Token Login support to Kotlin DSL
Closes gh-15698
This commit is contained in:
parent
3b2afd7a06
commit
81e4c7273a
@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.web
|
|||||||
|
|
||||||
import jakarta.servlet.Filter
|
import jakarta.servlet.Filter
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import org.checkerframework.checker.units.qual.C
|
|
||||||
import org.springframework.context.ApplicationContext
|
import org.springframework.context.ApplicationContext
|
||||||
import org.springframework.security.authentication.AuthenticationManager
|
import org.springframework.security.authentication.AuthenticationManager
|
||||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
|
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
|
||||||
@ -104,7 +103,10 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||||||
* @param configurer
|
* @param configurer
|
||||||
* the [SecurityConfigurerAdapter] for further customizations
|
* the [SecurityConfigurerAdapter] for further customizations
|
||||||
*/
|
*/
|
||||||
fun <C : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> apply(configurer: C, configuration: C.() -> Unit = { }): C {
|
fun <C : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> apply(
|
||||||
|
configurer: C,
|
||||||
|
configuration: C.() -> Unit = { }
|
||||||
|
): C {
|
||||||
return this.http.apply(configurer).apply(configuration)
|
return this.http.apply(configurer).apply(configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +136,10 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||||||
* the [HttpSecurity] for further customizations
|
* the [HttpSecurity] for further customizations
|
||||||
* @since 6.2
|
* @since 6.2
|
||||||
*/
|
*/
|
||||||
fun <C : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> with(configurer: C, configuration: C.() -> Unit = { }): HttpSecurity? {
|
fun <C : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> with(
|
||||||
|
configurer: C,
|
||||||
|
configuration: C.() -> Unit = { }
|
||||||
|
): HttpSecurity? {
|
||||||
return this.http.with(configurer, configuration)
|
return this.http.with(configurer, configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +304,8 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||||||
* @since 5.7
|
* @since 5.7
|
||||||
*/
|
*/
|
||||||
fun authorizeHttpRequests(authorizeHttpRequestsConfiguration: AuthorizeHttpRequestsDsl.() -> Unit) {
|
fun authorizeHttpRequests(authorizeHttpRequestsConfiguration: AuthorizeHttpRequestsDsl.() -> Unit) {
|
||||||
val authorizeHttpRequestsCustomizer = AuthorizeHttpRequestsDsl(this.context).apply(authorizeHttpRequestsConfiguration).get()
|
val authorizeHttpRequestsCustomizer =
|
||||||
|
AuthorizeHttpRequestsDsl(this.context).apply(authorizeHttpRequestsConfiguration).get()
|
||||||
this.http.authorizeHttpRequests(authorizeHttpRequestsCustomizer)
|
this.http.authorizeHttpRequests(authorizeHttpRequestsCustomizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -965,6 +971,36 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||||||
this.http.oidcLogout(oidcLogoutCustomizer)
|
this.http.oidcLogout(oidcLogoutCustomizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures One-Time Token Login Support.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* @Configuration
|
||||||
|
* @EnableWebSecurity
|
||||||
|
* class SecurityConfig {
|
||||||
|
*
|
||||||
|
* @Bean
|
||||||
|
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
* http {
|
||||||
|
* oneTimeTokenLogin {
|
||||||
|
* generatedOneTimeTokenHandler = MyMagicLinkGeneratedOneTimeTokenHandler()
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* return http.build()
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* @since 6.4
|
||||||
|
* @param oneTimeTokenLoginConfiguration custom configuration to configure one-time token login
|
||||||
|
*/
|
||||||
|
fun oneTimeTokenLogin(oneTimeTokenLoginConfiguration: OneTimeTokenLoginDsl.() -> Unit) {
|
||||||
|
val oneTimeTokenLoginCustomizer = OneTimeTokenLoginDsl().apply(oneTimeTokenLoginConfiguration).get()
|
||||||
|
this.http.oneTimeTokenLogin(oneTimeTokenLoginCustomizer)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Remember Me authentication.
|
* Configures Remember Me authentication.
|
||||||
*
|
*
|
||||||
@ -1050,7 +1086,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||||||
* (i.e. known) with Spring Security.
|
* (i.e. known) with Spring Security.
|
||||||
*/
|
*/
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
inline fun <reified T: Filter> addFilterAt(filter: Filter) {
|
inline fun <reified T : Filter> addFilterAt(filter: Filter) {
|
||||||
this.addFilterAt(filter, T::class.java)
|
this.addFilterAt(filter, T::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1109,7 +1145,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||||||
* (i.e. known) with Spring Security.
|
* (i.e. known) with Spring Security.
|
||||||
*/
|
*/
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
inline fun <reified T: Filter> addFilterAfter(filter: Filter) {
|
inline fun <reified T : Filter> addFilterAfter(filter: Filter) {
|
||||||
this.addFilterAfter(filter, T::class.java)
|
this.addFilterAfter(filter, T::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1168,7 +1204,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||||||
* (i.e. known) with Spring Security.
|
* (i.e. known) with Spring Security.
|
||||||
*/
|
*/
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
inline fun <reified T: Filter> addFilterBefore(filter: Filter) {
|
inline fun <reified T : Filter> addFilterBefore(filter: Filter) {
|
||||||
this.addFilterBefore(filter, T::class.java)
|
this.addFilterBefore(filter, T::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2024 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.config.annotation.web
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider
|
||||||
|
import org.springframework.security.authentication.ott.OneTimeTokenService
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.ott.OneTimeTokenLoginConfigurer
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationConverter
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationFailureHandler
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
|
||||||
|
import org.springframework.security.web.authentication.ott.GeneratedOneTimeTokenHandler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Kotlin DSL to configure [HttpSecurity] OAuth 2.0 login using idiomatic Kotlin code.
|
||||||
|
*
|
||||||
|
* @author Max Batischev
|
||||||
|
* @since 6.4
|
||||||
|
* @property oneTimeTokenService configures the [OneTimeTokenService] used to generate and consume
|
||||||
|
* @property authenticationConverter Use this [AuthenticationConverter] when converting incoming requests to an authentication
|
||||||
|
* @property authenticationFailureHandler the [AuthenticationFailureHandler] to use when authentication
|
||||||
|
* @property authenticationSuccessHandler the [AuthenticationSuccessHandler] to be used
|
||||||
|
* @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 generateTokenUrl the URL that a One-Time Token generate request will be processed
|
||||||
|
* @property generatedOneTimeTokenHandler the strategy to be used to handle generated one-time tokens
|
||||||
|
* @property authenticationProvider the [AuthenticationProvider] to use when authenticating the user
|
||||||
|
*/
|
||||||
|
@SecurityMarker
|
||||||
|
class OneTimeTokenLoginDsl {
|
||||||
|
var oneTimeTokenService: OneTimeTokenService? = null
|
||||||
|
var authenticationConverter: AuthenticationConverter? = null
|
||||||
|
var authenticationFailureHandler: AuthenticationFailureHandler? = null
|
||||||
|
var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
|
||||||
|
var defaultSubmitPageUrl: String? = null
|
||||||
|
var loginProcessingUrl: String? = null
|
||||||
|
var generateTokenUrl: String? = null
|
||||||
|
var showDefaultSubmitPage: Boolean? = true
|
||||||
|
var generatedOneTimeTokenHandler: GeneratedOneTimeTokenHandler? = null
|
||||||
|
var authenticationProvider: AuthenticationProvider? = null
|
||||||
|
|
||||||
|
internal fun get(): (OneTimeTokenLoginConfigurer<HttpSecurity>) -> Unit {
|
||||||
|
return { oneTimeTokenLoginConfigurer ->
|
||||||
|
oneTimeTokenService?.also { oneTimeTokenLoginConfigurer.oneTimeTokenService(oneTimeTokenService) }
|
||||||
|
authenticationConverter?.also { oneTimeTokenLoginConfigurer.authenticationConverter(authenticationConverter) }
|
||||||
|
authenticationFailureHandler?.also {
|
||||||
|
oneTimeTokenLoginConfigurer.authenticationFailureHandler(
|
||||||
|
authenticationFailureHandler
|
||||||
|
)
|
||||||
|
}
|
||||||
|
authenticationSuccessHandler?.also {
|
||||||
|
oneTimeTokenLoginConfigurer.authenticationSuccessHandler(
|
||||||
|
authenticationSuccessHandler
|
||||||
|
)
|
||||||
|
}
|
||||||
|
defaultSubmitPageUrl?.also { oneTimeTokenLoginConfigurer.defaultSubmitPageUrl(defaultSubmitPageUrl) }
|
||||||
|
showDefaultSubmitPage?.also { oneTimeTokenLoginConfigurer.showDefaultSubmitPage(showDefaultSubmitPage!!) }
|
||||||
|
loginProcessingUrl?.also { oneTimeTokenLoginConfigurer.loginProcessingUrl(loginProcessingUrl) }
|
||||||
|
generateTokenUrl?.also { oneTimeTokenLoginConfigurer.generateTokenUrl(generateTokenUrl) }
|
||||||
|
generatedOneTimeTokenHandler?.also {
|
||||||
|
oneTimeTokenLoginConfigurer.generatedOneTimeTokenHandler(
|
||||||
|
generatedOneTimeTokenHandler
|
||||||
|
)
|
||||||
|
}
|
||||||
|
authenticationProvider?.also { oneTimeTokenLoginConfigurer.authenticationProvider(authenticationProvider) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2024 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.config.annotation.web
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
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.OneTimeToken
|
||||||
|
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
|
||||||
|
import org.springframework.security.config.test.SpringTestContextExtension
|
||||||
|
import org.springframework.security.core.userdetails.PasswordEncodedUser
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService
|
||||||
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||||
|
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
|
||||||
|
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
|
||||||
|
import org.springframework.security.web.SecurityFilterChain
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
|
||||||
|
import org.springframework.security.web.authentication.ott.GeneratedOneTimeTokenHandler
|
||||||
|
import org.springframework.security.web.authentication.ott.RedirectGeneratedOneTimeTokenHandler
|
||||||
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
|
||||||
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for [OneTimeTokenLoginDsl]
|
||||||
|
*
|
||||||
|
* @author Max Batischev
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringTestContextExtension::class)
|
||||||
|
class OneTimeTokenLoginDslTests {
|
||||||
|
@JvmField
|
||||||
|
val spring = SpringTestContext(this)
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private lateinit var mockMvc: MockMvc
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `oneTimeToken when correct token then can authenticate`() {
|
||||||
|
spring.register(OneTimeTokenConfig::class.java).autowire()
|
||||||
|
this.mockMvc.perform(
|
||||||
|
MockMvcRequestBuilders.post("/ott/generate").param("username", "user")
|
||||||
|
.with(SecurityMockMvcRequestPostProcessors.csrf())
|
||||||
|
).andExpectAll(
|
||||||
|
MockMvcResultMatchers
|
||||||
|
.status()
|
||||||
|
.isFound(),
|
||||||
|
MockMvcResultMatchers
|
||||||
|
.redirectedUrl("/login/ott")
|
||||||
|
)
|
||||||
|
|
||||||
|
val token = TestGeneratedOneTimeTokenHandler.lastToken?.tokenValue
|
||||||
|
|
||||||
|
this.mockMvc.perform(
|
||||||
|
MockMvcRequestBuilders.post("/login/ott").param("token", token)
|
||||||
|
.with(SecurityMockMvcRequestPostProcessors.csrf())
|
||||||
|
)
|
||||||
|
.andExpectAll(
|
||||||
|
MockMvcResultMatchers.status().isFound(),
|
||||||
|
MockMvcResultMatchers.redirectedUrl("/"),
|
||||||
|
SecurityMockMvcResultMatchers.authenticated()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `oneTimeToken when different authentication urls then can authenticate`() {
|
||||||
|
spring.register(OneTimeTokenDifferentUrlsConfig::class.java).autowire()
|
||||||
|
this.mockMvc.perform(
|
||||||
|
MockMvcRequestBuilders.post("/generateurl").param("username", "user")
|
||||||
|
.with(SecurityMockMvcRequestPostProcessors.csrf())
|
||||||
|
)
|
||||||
|
.andExpectAll(MockMvcResultMatchers.status().isFound(), MockMvcResultMatchers.redirectedUrl("/redirected"))
|
||||||
|
|
||||||
|
val token = TestGeneratedOneTimeTokenHandler.lastToken?.tokenValue
|
||||||
|
|
||||||
|
this.mockMvc.perform(
|
||||||
|
MockMvcRequestBuilders.post("/loginprocessingurl").param("token", token)
|
||||||
|
.with(SecurityMockMvcRequestPostProcessors.csrf())
|
||||||
|
)
|
||||||
|
.andExpectAll(
|
||||||
|
MockMvcResultMatchers.status().isFound(),
|
||||||
|
MockMvcResultMatchers.redirectedUrl("/authenticated"),
|
||||||
|
SecurityMockMvcResultMatchers.authenticated()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@Import(UserDetailsServiceConfig::class)
|
||||||
|
open class OneTimeTokenConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
// @formatter:off
|
||||||
|
http {
|
||||||
|
authorizeHttpRequests {
|
||||||
|
authorize(anyRequest, authenticated)
|
||||||
|
}
|
||||||
|
oneTimeTokenLogin {
|
||||||
|
generatedOneTimeTokenHandler = TestGeneratedOneTimeTokenHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @formatter:on
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@Import(UserDetailsServiceConfig::class)
|
||||||
|
open class OneTimeTokenDifferentUrlsConfig {
|
||||||
|
@Bean
|
||||||
|
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
// @formatter:off
|
||||||
|
http {
|
||||||
|
authorizeHttpRequests {
|
||||||
|
authorize(anyRequest, authenticated)
|
||||||
|
}
|
||||||
|
oneTimeTokenLogin {
|
||||||
|
generateTokenUrl = "/generateurl"
|
||||||
|
generatedOneTimeTokenHandler = TestGeneratedOneTimeTokenHandler("/redirected")
|
||||||
|
loginProcessingUrl = "/loginprocessingurl"
|
||||||
|
authenticationSuccessHandler = SimpleUrlAuthenticationSuccessHandler("/authenticated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @formatter:on
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
open class UserDetailsServiceConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun userDetailsService(): UserDetailsService =
|
||||||
|
InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin())
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestGeneratedOneTimeTokenHandler : GeneratedOneTimeTokenHandler {
|
||||||
|
private val delegate: GeneratedOneTimeTokenHandler
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.delegate = RedirectGeneratedOneTimeTokenHandler("/login/ott")
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(redirectUrl: String?) {
|
||||||
|
this.delegate = RedirectGeneratedOneTimeTokenHandler(redirectUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(request: HttpServletRequest, response: HttpServletResponse, oneTimeToken: OneTimeToken) {
|
||||||
|
lastToken = oneTimeToken
|
||||||
|
delegate.handle(request, response, oneTimeToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var lastToken: OneTimeToken? = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -77,11 +77,11 @@ import org.springframework.mail.SimpleMailMessage;
|
|||||||
import org.springframework.mail.javamail.JavaMailSender;
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
|
||||||
@Component <1>
|
@Component <1>
|
||||||
public class MagicLinkGeneratedOneTimeTokenSuccessHandler implements GeneratedOneTimeTokenSuccessHandler {
|
public class MagicLinkGeneratedOneTimeTokenSuccessHandler implements GeneratedOneTimeTokenHandler {
|
||||||
|
|
||||||
private final MailSender mailSender;
|
private final MailSender mailSender;
|
||||||
|
|
||||||
private final GeneratedOneTimeTokenSuccessHandler redirectHandler = new RedirectGeneratedOneTimeTokenSuccessHandler("/ott/sent");
|
private final GeneratedOneTimeTokenHandler redirectHandler = new RedirectGeneratedOneTimeTokenHandler("/ott/sent");
|
||||||
|
|
||||||
// constructor omitted
|
// constructor omitted
|
||||||
|
|
||||||
@ -115,6 +115,65 @@ class PageController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun filterChain(
|
||||||
|
http: HttpSecurity,
|
||||||
|
magicLinkSender: MagicLinkGeneratedOneTimeTokenSuccessHandler?
|
||||||
|
): SecurityFilterChain {
|
||||||
|
http{
|
||||||
|
formLogin {}
|
||||||
|
oneTimeTokenLogin { }
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import org.springframework.mail.SimpleMailMessage;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
|
||||||
|
@Component (1)
|
||||||
|
class MagicLinkGeneratedOneTimeTokenSuccessHandler(
|
||||||
|
private val mailSender: MailSender,
|
||||||
|
private val redirectHandler: GeneratedOneTimeTokenHandler = RedirectGeneratedOneTimeTokenHandler("/ott/sent")
|
||||||
|
) : GeneratedOneTimeTokenHandler {
|
||||||
|
|
||||||
|
override fun handle(request: HttpServletRequest, response: HttpServletResponse, oneTimeToken: OneTimeToken) {
|
||||||
|
val builder = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||||
|
.replacePath(request.contextPath)
|
||||||
|
.replaceQuery(null)
|
||||||
|
.fragment(null)
|
||||||
|
.path("/login/ott")
|
||||||
|
.queryParam("token", oneTimeToken.getTokenValue()) (2)
|
||||||
|
val magicLink = builder.toUriString()
|
||||||
|
val email = getUserEmail(oneTimeToken.getUsername()) (3)
|
||||||
|
this.mailSender.send(email, "Your Spring Security One Time Token", "Use the following link to sign in into the application: $magicLink")(4)
|
||||||
|
this.redirectHandler.handle(request, response, oneTimeToken) (5)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUserEmail(): String {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
class PageController {
|
||||||
|
|
||||||
|
@GetMapping("/ott/sent")
|
||||||
|
fun ottSent(): String {
|
||||||
|
return "my-template"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
@ -122,7 +181,7 @@ class PageController {
|
|||||||
<2> Create a login processing URL with the `token` as a query param
|
<2> Create a login processing URL with the `token` as a query param
|
||||||
<3> Retrieve the user's email based on the username
|
<3> Retrieve the user's email based on the username
|
||||||
<4> Use the `JavaMailSender` API to send the email to the user with the magic link
|
<4> Use the `JavaMailSender` API to send the email to the user with the magic link
|
||||||
<5> Use the `RedirectGeneratedOneTimeTokenSuccessHandler` to perform a redirect to your desired URL
|
<5> Use the `RedirectGeneratedOneTimeTokenHandler` to perform a redirect to your desired URL
|
||||||
|
|
||||||
The email content will look similar to:
|
The email content will look similar to:
|
||||||
|
|
||||||
@ -161,7 +220,34 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenSuccessHandler implements GeneratedOneTimeTokenSuccessHandler {
|
public class MagicLinkGeneratedOneTimeTokenSuccessHandler implements GeneratedOneTimeTokenHandler {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
http {
|
||||||
|
//...
|
||||||
|
formLogin { }
|
||||||
|
oneTimeTokenLogin {
|
||||||
|
generateTokenUrl = "/ott/my-generate-url"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class MagicLinkGeneratedOneTimeTokenSuccessHandler : GeneratedOneTimeTokenHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -202,6 +288,33 @@ public class MagicLinkGeneratedOneTimeTokenSuccessHandler implements GeneratedOn
|
|||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
http {
|
||||||
|
//...
|
||||||
|
formLogin { }
|
||||||
|
oneTimeTokenLogin {
|
||||||
|
submitPageUrl = "/ott/submit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class MagicLinkGeneratedOneTimeTokenSuccessHandler : GeneratedOneTimeTokenHandler {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
[[disabling-default-submit-page]]
|
[[disabling-default-submit-page]]
|
||||||
@ -251,6 +364,45 @@ public class MagicLinkGeneratedOneTimeTokenSuccessHandler implements GeneratedOn
|
|||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
http {
|
||||||
|
authorizeHttpRequests {
|
||||||
|
authorize("/my-ott-submit", authenticated)
|
||||||
|
authorize(anyRequest, authenticated)
|
||||||
|
}
|
||||||
|
formLogin { }
|
||||||
|
oneTimeTokenLogin {
|
||||||
|
showDefaultSubmitPage = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
class MyController {
|
||||||
|
|
||||||
|
@GetMapping("/my-ott-submit")
|
||||||
|
fun ottSubmitPage(): String {
|
||||||
|
return "my-ott-submit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class MagicLinkGeneratedOneTimeTokenSuccessHandler : GeneratedOneTimeTokenHandler {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user