mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-11-04 00:28:54 +00:00 
			
		
		
		
	The names of variables and methods have been adjusted in accordance with the names of the one-time token login API components. Issue gh-15114
		
			
				
	
	
		
			548 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			548 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
[[one-time-token-login]]
 | 
						|
= One-Time Token Login
 | 
						|
 | 
						|
Spring Security offers support for One-Time Token (OTT) authentication via the `oneTimeTokenLogin()` DSL.
 | 
						|
Before diving into implementation details, it's important to clarify the scope of the OTT feature within the framework, highlighting what is supported and what isn't.
 | 
						|
 | 
						|
== Understanding One-Time Tokens vs. One-Time Passwords
 | 
						|
 | 
						|
It's common to confuse One-Time Tokens (OTT) with https://en.wikipedia.org/wiki/One-time_password[One-Time Passwords] (OTP), but in Spring Security, these concepts differ in several key ways.
 | 
						|
For clarity, we'll assume OTP refers to https://en.wikipedia.org/wiki/Time-based_one-time_password[TOTP] (Time-Based One-Time Password) or https://en.wikipedia.org/wiki/HMAC-based_one-time_password[HOTP] (HMAC-Based One-Time Password).
 | 
						|
 | 
						|
=== Setup Requirements
 | 
						|
 | 
						|
- OTT: No initial setup is required. The user doesn't need to configure anything in advance.
 | 
						|
- OTP: Typically requires setup, such as generating and sharing a secret key with an external tool to produce the one-time passwords.
 | 
						|
 | 
						|
=== Token Delivery
 | 
						|
 | 
						|
- OTT: Usually a custom javadoc:org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler[] must be implemented, responsible for delivering the token to the end user.
 | 
						|
- OTP: The token is often generated by an external tool, so there's no need to send it to the user via the application.
 | 
						|
 | 
						|
=== Token Generation
 | 
						|
 | 
						|
- OTT: The javadoc:org.springframework.security.authentication.ott.OneTimeTokenService#generate(org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest)[] method requires a javadoc:org.springframework.security.authentication.ott.OneTimeToken[] to be returned, emphasizing server-side generation.
 | 
						|
- OTP: The token is not necessarily generated on the server side, it's often created by the client using the shared secret.
 | 
						|
 | 
						|
In summary, One-Time Tokens (OTT) provide a way to authenticate users without additional account setup, differentiating them from One-Time Passwords (OTP), which typically involve a more complex setup process and rely on external tools for token generation.
 | 
						|
 | 
						|
The One-Time Token Login works in two major steps.
 | 
						|
 | 
						|
1. User requests a token by submitting their user identifier, usually the username, and the token is delivered to them, often as a Magic Link, via e-mail, SMS, etc.
 | 
						|
2. User submits the token to the one-time token login endpoint and, if valid, the user gets logged in.
 | 
						|
 | 
						|
In the following sections we will explore how to configure OTT Login for your needs.
 | 
						|
 | 
						|
- <<default-pages,Understanding the integration with the default generated login page>>
 | 
						|
- <<sending-token-to-user,Sending the token to the user>>
 | 
						|
- <<changing-submit-page-url,Configuring the One-Time Token submit page>>
 | 
						|
- <<changing-generate-url,Changing the One-Time Token generate URL>>
 | 
						|
- <<customize-generate-consume-token,Customize how to generate and consume tokens>>
 | 
						|
 | 
						|
[[default-pages]]
 | 
						|
== Default Login Page and Default One-Time Token Submit Page
 | 
						|
 | 
						|
The `oneTimeTokenLogin()` DSL can be used in conjunction with `formLogin()`, which will produce an additional One-Time Token Request Form in the xref:servlet/authentication/passwords/form.adoc[default generated login page].
 | 
						|
It will also set up the javadoc:org.springframework.security.web.authentication.ui.DefaultOneTimeTokenSubmitPageGeneratingFilter[] to generate a default One-Time Token submit page.
 | 
						|
 | 
						|
[[sending-token-to-user]]
 | 
						|
== Sending the Token to the User
 | 
						|
 | 
						|
It is not possible for Spring Security to reasonably determine the way the token should be delivered to your users.
 | 
						|
Therefore, a custom javadoc:org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler[] must be provided to deliver the token to the user based on your needs.
 | 
						|
One of the most common delivery strategies is a Magic Link, via e-mail, SMS, etc.
 | 
						|
In the following example, we are going to create a magic link and sent it to the user's email.
 | 
						|
 | 
						|
.One-Time Token Login Configuration
 | 
						|
[tabs]
 | 
						|
======
 | 
						|
Java::
 | 
						|
+
 | 
						|
[source,java,role="primary"]
 | 
						|
----
 | 
						|
@Configuration
 | 
						|
@EnableWebSecurity
 | 
						|
public class SecurityConfig {
 | 
						|
 | 
						|
    @Bean
 | 
						|
    public SecurityFilterChain filterChain(HttpSecurity http) {
 | 
						|
        http
 | 
						|
            // ...
 | 
						|
            .formLogin(Customizer.withDefaults())
 | 
						|
            .oneTimeTokenLogin(Customizer.withDefaults());
 | 
						|
        return http.build();
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
import org.springframework.mail.SimpleMailMessage;
 | 
						|
import org.springframework.mail.javamail.JavaMailSender;
 | 
						|
 | 
						|
@Component <1>
 | 
						|
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
 | 
						|
 | 
						|
    private final MailSender mailSender;
 | 
						|
 | 
						|
    private final OneTimeTokenGenerationSuccessHandler redirectHandler = new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
 | 
						|
 | 
						|
    // constructor omitted
 | 
						|
 | 
						|
    @Override
 | 
						|
    public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) throws IOException, ServletException {
 | 
						|
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
 | 
						|
                .replacePath(request.getContextPath())
 | 
						|
                .replaceQuery(null)
 | 
						|
                .fragment(null)
 | 
						|
                .path("/login/ott")
 | 
						|
                .queryParam("token", oneTimeToken.getTokenValue()); <2>
 | 
						|
        String magicLink = builder.toUriString();
 | 
						|
        String 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 String getUserEmail() {
 | 
						|
        // ...
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
@Controller
 | 
						|
class PageController {
 | 
						|
 | 
						|
    @GetMapping("/ott/sent")
 | 
						|
    String ottSent() {
 | 
						|
        return "my-template";
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
----
 | 
						|
 | 
						|
Kotlin::
 | 
						|
+
 | 
						|
[source,kotlin,role="secondary"]
 | 
						|
----
 | 
						|
@Configuration
 | 
						|
@EnableWebSecurity
 | 
						|
class SecurityConfig {
 | 
						|
 | 
						|
        @Bean
 | 
						|
        open fun filterChain(http: HttpSecurity): SecurityFilterChain {
 | 
						|
            http{
 | 
						|
                formLogin {}
 | 
						|
                oneTimeTokenLogin {  }
 | 
						|
            }
 | 
						|
            return http.build()
 | 
						|
        }
 | 
						|
}
 | 
						|
 | 
						|
import org.springframework.mail.SimpleMailMessage;
 | 
						|
import org.springframework.mail.javamail.JavaMailSender;
 | 
						|
 | 
						|
@Component (1)
 | 
						|
class MagicLinkOneTimeTokenGenerationSuccessHandler(
 | 
						|
    private val mailSender: MailSender,
 | 
						|
    private val redirectHandler: OneTimeTokenGenerationSuccessHandler = RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent")
 | 
						|
) : OneTimeTokenGenerationSuccessHandler {
 | 
						|
 | 
						|
    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"
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
----
 | 
						|
======
 | 
						|
 | 
						|
<1> Make the `MagicLinkOneTimeTokenGenerationSuccessHandler` a Spring bean
 | 
						|
<2> Create a login processing URL with the `token` as a query param
 | 
						|
<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
 | 
						|
<5> Use the `RedirectOneTimeTokenGenerationSuccessHandler` to perform a redirect to your desired URL
 | 
						|
 | 
						|
The email content will look similar to:
 | 
						|
 | 
						|
> Use the following link to sign in into the application: \http://localhost:8080/login/ott?token=a830c444-29d8-4d98-9b46-6aba7b22fe5b
 | 
						|
 | 
						|
The default submit page will detect that the URL has the `token` query param and will automatically fill the form field with the token value.
 | 
						|
 | 
						|
[[changing-generate-url]]
 | 
						|
== Changing the One-Time Token Generate URL
 | 
						|
 | 
						|
By default, the javadoc:org.springframework.security.web.authentication.ott.GenerateOneTimeTokenFilter[] listens to `POST /ott/generate` requests.
 | 
						|
That URL can be changed by using the `generateTokenUrl(String)` DSL method:
 | 
						|
 | 
						|
.Changing the Generate URL
 | 
						|
[tabs]
 | 
						|
======
 | 
						|
Java::
 | 
						|
+
 | 
						|
[source,java,role="primary"]
 | 
						|
----
 | 
						|
@Configuration
 | 
						|
@EnableWebSecurity
 | 
						|
public class SecurityConfig {
 | 
						|
 | 
						|
    @Bean
 | 
						|
    public SecurityFilterChain filterChain(HttpSecurity http) {
 | 
						|
        http
 | 
						|
            // ...
 | 
						|
            .formLogin(Customizer.withDefaults())
 | 
						|
            .oneTimeTokenLogin((ott) -> ott
 | 
						|
                .generateTokenUrl("/ott/my-generate-url")
 | 
						|
            );
 | 
						|
        return http.build();
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
@Component
 | 
						|
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
 | 
						|
    // ...
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
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 MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
 | 
						|
     // ...
 | 
						|
}
 | 
						|
----
 | 
						|
======
 | 
						|
 | 
						|
[[changing-submit-page-url]]
 | 
						|
== Changing the Default Submit Page URL
 | 
						|
 | 
						|
The default One-Time Token submit page is generated by the javadoc:org.springframework.security.web.authentication.ui.DefaultOneTimeTokenSubmitPageGeneratingFilter[] and listens to `GET /login/ott`.
 | 
						|
The URL can also be changed, like so:
 | 
						|
 | 
						|
.Configuring the Default Submit Page URL
 | 
						|
[tabs]
 | 
						|
======
 | 
						|
Java::
 | 
						|
+
 | 
						|
[source,java,role="primary"]
 | 
						|
----
 | 
						|
@Configuration
 | 
						|
@EnableWebSecurity
 | 
						|
public class SecurityConfig {
 | 
						|
 | 
						|
    @Bean
 | 
						|
    public SecurityFilterChain filterChain(HttpSecurity http) {
 | 
						|
        http
 | 
						|
            // ...
 | 
						|
            .formLogin(Customizer.withDefaults())
 | 
						|
            .oneTimeTokenLogin((ott) -> ott
 | 
						|
                .submitPageUrl("/ott/submit")
 | 
						|
            );
 | 
						|
        return http.build();
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
@Component
 | 
						|
public class MagicLinkGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
 | 
						|
    // ...
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
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 MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
 | 
						|
     // ...
 | 
						|
}
 | 
						|
----
 | 
						|
======
 | 
						|
 | 
						|
[[disabling-default-submit-page]]
 | 
						|
== Disabling the Default Submit Page
 | 
						|
 | 
						|
If you want to use your own One-Time Token submit page, you can disable the default page and then provide your own endpoint.
 | 
						|
 | 
						|
.Disabling the Default Submit Page
 | 
						|
[tabs]
 | 
						|
======
 | 
						|
Java::
 | 
						|
+
 | 
						|
[source,java,role="primary"]
 | 
						|
----
 | 
						|
@Configuration
 | 
						|
@EnableWebSecurity
 | 
						|
public class SecurityConfig {
 | 
						|
 | 
						|
    @Bean
 | 
						|
    public SecurityFilterChain filterChain(HttpSecurity http) {
 | 
						|
        http
 | 
						|
            .authorizeHttpRequests((authorize) -> authorize
 | 
						|
                .requestMatchers("/my-ott-submit").permitAll()
 | 
						|
                .anyRequest().authenticated()
 | 
						|
            )
 | 
						|
            .formLogin(Customizer.withDefaults())
 | 
						|
            .oneTimeTokenLogin((ott) -> ott
 | 
						|
                .showDefaultSubmitPage(false)
 | 
						|
            );
 | 
						|
        return http.build();
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
@Controller
 | 
						|
public class MyController {
 | 
						|
 | 
						|
    @GetMapping("/my-ott-submit")
 | 
						|
    public String ottSubmitPage() {
 | 
						|
        return "my-ott-submit";
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
@Component
 | 
						|
public class OneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
 | 
						|
    // ...
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
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 MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
 | 
						|
     // ...
 | 
						|
}
 | 
						|
----
 | 
						|
======
 | 
						|
 | 
						|
[[customize-generate-consume-token]]
 | 
						|
== Customize How to Generate and Consume One-Time Tokens
 | 
						|
 | 
						|
The interface that define the common operations for generating and consuming one-time tokens is the javadoc:org.springframework.security.authentication.ott.OneTimeTokenService[].
 | 
						|
Spring Security uses the javadoc:org.springframework.security.authentication.ott.InMemoryOneTimeTokenService[] as the default implementation of that interface, if none is provided.
 | 
						|
For production environments consider using javadoc:org.springframework.security.authentication.ott.JdbcOneTimeTokenService[].
 | 
						|
 | 
						|
Some of the most common reasons to customize the `OneTimeTokenService` are, but not limited to:
 | 
						|
 | 
						|
- Changing the one-time token expire time
 | 
						|
- Storing more information from the generate token request
 | 
						|
- Changing how the token value is created
 | 
						|
- Additional validation when consuming a one-time token
 | 
						|
 | 
						|
There are two options to customize the `OneTimeTokenService`.
 | 
						|
One option is to provide it as a bean, so it can be automatically be picked-up by the `oneTimeTokenLogin()` DSL:
 | 
						|
 | 
						|
.Passing the OneTimeTokenService as a Bean
 | 
						|
[tabs]
 | 
						|
======
 | 
						|
Java::
 | 
						|
+
 | 
						|
[source,java,role="primary"]
 | 
						|
----
 | 
						|
@Configuration
 | 
						|
@EnableWebSecurity
 | 
						|
public class SecurityConfig {
 | 
						|
 | 
						|
    @Bean
 | 
						|
    public SecurityFilterChain filterChain(HttpSecurity http) {
 | 
						|
        http
 | 
						|
            // ...
 | 
						|
            .formLogin(Customizer.withDefaults())
 | 
						|
            .oneTimeTokenLogin(Customizer.withDefaults());
 | 
						|
        return http.build();
 | 
						|
    }
 | 
						|
 | 
						|
    @Bean
 | 
						|
    public OneTimeTokenService oneTimeTokenService() {
 | 
						|
        return new MyCustomOneTimeTokenService();
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
@Component
 | 
						|
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
 | 
						|
    // ...
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Kotlin::
 | 
						|
+
 | 
						|
[source,kotlin,role="secondary"]
 | 
						|
----
 | 
						|
@Configuration
 | 
						|
@EnableWebSecurity
 | 
						|
class SecurityConfig {
 | 
						|
 | 
						|
    @Bean
 | 
						|
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
 | 
						|
        http {
 | 
						|
            //...
 | 
						|
            formLogin { }
 | 
						|
            oneTimeTokenLogin { }
 | 
						|
        }
 | 
						|
        return http.build()
 | 
						|
    }
 | 
						|
 | 
						|
    @Bean
 | 
						|
    open fun oneTimeTokenService(): OneTimeTokenService {
 | 
						|
        return MyCustomOneTimeTokenService()
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
@Component
 | 
						|
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
 | 
						|
     // ...
 | 
						|
}
 | 
						|
----
 | 
						|
======
 | 
						|
 | 
						|
The second option is to pass the `OneTimeTokenService` instance to the DSL, which is useful if there are multiple `SecurityFilterChain` and a different `OneTimeTokenService` is needed for each of them.
 | 
						|
 | 
						|
.Passing the OneTimeTokenService using the DSL
 | 
						|
[tabs]
 | 
						|
======
 | 
						|
Java::
 | 
						|
+
 | 
						|
[source,java,role="primary"]
 | 
						|
----
 | 
						|
@Configuration
 | 
						|
@EnableWebSecurity
 | 
						|
public class SecurityConfig {
 | 
						|
 | 
						|
    @Bean
 | 
						|
    public SecurityFilterChain filterChain(HttpSecurity http) {
 | 
						|
        http
 | 
						|
            // ...
 | 
						|
            .formLogin(Customizer.withDefaults())
 | 
						|
            .oneTimeTokenLogin((ott) -> ott
 | 
						|
                .oneTimeTokenService(new MyCustomOneTimeTokenService())
 | 
						|
            );
 | 
						|
        return http.build();
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
@Component
 | 
						|
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
 | 
						|
    // ...
 | 
						|
}
 | 
						|
----
 | 
						|
 | 
						|
Kotlin::
 | 
						|
+
 | 
						|
[source,kotlin,role="secondary"]
 | 
						|
----
 | 
						|
@Configuration
 | 
						|
@EnableWebSecurity
 | 
						|
class SecurityConfig {
 | 
						|
 | 
						|
    @Bean
 | 
						|
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
 | 
						|
        http {
 | 
						|
            //...
 | 
						|
            formLogin { }
 | 
						|
            oneTimeTokenLogin {
 | 
						|
                oneTimeTokenService = MyCustomOneTimeTokenService()
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return http.build()
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
@Component
 | 
						|
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
 | 
						|
     // ...
 | 
						|
}
 | 
						|
----
 | 
						|
======
 |