mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-31 14:48:54 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			581 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			581 lines
		
	
	
		
			18 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.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler[] 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.reactive.ReactiveOneTimeTokenService#generate(org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest)[] method requires a javadoc:org.springframework.security.authentication.ott.OneTimeToken[], wrapped in Mono, 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.server.ui.OneTimeTokenSubmitPageGeneratingWebFilter[] 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.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler[] 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
 | |
| @EnableWebFluxSecurity
 | |
| public class SecurityConfig {
 | |
| 
 | |
|     @Bean
 | |
|     public SecurityWebFilterChain filterChain(ServerHttpSecurity 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 ServerOneTimeTokenGenerationSuccessHandler {
 | |
| 
 | |
|     private final MailSender mailSender;
 | |
| 
 | |
|     private final ServerOneTimeTokenGenerationSuccessHandler redirectHandler = new ServerRedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
 | |
| 
 | |
|     // constructor omitted
 | |
| 
 | |
|     @Override
 | |
|     public Mono<Void> handle(ServerWebExchange exchange, OneTimeToken oneTimeToken) {
 | |
|         return Mono.just(exchange.getRequest())
 | |
| 				.map((request) ->
 | |
| 					UriComponentsBuilder.fromUri(request.getURI())
 | |
| 						.replacePath(request.getPath().contextPath().value())
 | |
| 						.replaceQuery(null)
 | |
| 						.fragment(null)
 | |
| 						.path("/login/ott")
 | |
| 						.queryParam("token", oneTimeToken.getTokenValue())
 | |
| 						.toUriString() <2>
 | |
| 					)
 | |
| 			.flatMap((uri) -> this.mailSender.send(getUserEmail(oneTimeToken.getUsername()), <3>
 | |
| 					"Use the following link to sign in into the application: " + magicLink)) <4>
 | |
| 			.then(this.redirectHandler.handle(exchange, oneTimeToken)); <5>
 | |
|     }
 | |
| 
 | |
|     private String getUserEmail() {
 | |
|         // ...
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| @Controller
 | |
| class PageController {
 | |
| 
 | |
|     @GetMapping("/ott/sent")
 | |
|     String ottSent() {
 | |
|         return "my-template";
 | |
|     }
 | |
| 
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Configuration
 | |
| @EnableWebFluxSecurity
 | |
| class SecurityConfig {
 | |
| 
 | |
|          open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|              return http {
 | |
|                 authorizeExchange {
 | |
|                     authorize(anyExchange, authenticated)
 | |
|                  }
 | |
|                  oneTimeTokenLogin { }
 | |
|              }
 | |
|          }
 | |
| 
 | |
| }
 | |
| 
 | |
| @Component (1)
 | |
| class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | |
| 
 | |
|     private val redirectStrategy: ServerRedirectStrategy = DefaultServerRedirectStrategy()
 | |
| 
 | |
|     override fun handle(exchange: ServerWebExchange, oneTimeToken: OneTimeToken): Mono<Void> {
 | |
|         val builder = UriComponentsBuilder.fromUri(exchange.request.uri)
 | |
|             .replacePath(null)
 | |
|             .replaceQuery(null)
 | |
|             .fragment(null)
 | |
|             .path("/login/ott")
 | |
|             .queryParam("token", oneTimeToken.getTokenValue()) (2)
 | |
|         val magicLink = builder.toUriString()
 | |
|         builder.replacePath(null)
 | |
|             .replaceQuery(null)
 | |
|             .path("/ott/sent")
 | |
|         val redirectLink = builder.toUriString()
 | |
|         return this.mailSender.send(
 | |
|             getUserEmail(oneTimeToken.getUsername()), (3)
 | |
|             "Use the following link to sign in into the application: $magicLink") (4)
 | |
|         .then(this.redirectStrategy.sendRedirect(exchange, URI.create(redirectLink))) (5)
 | |
|     }
 | |
| 
 | |
|         private String getUserEmail() {
 | |
|             // ...
 | |
|         }
 | |
| }
 | |
| 
 | |
| @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 `MailSender` API to send the email to the user with the magic link
 | |
| <5> Use the `ServerRedirectStrategy` 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.server.authentication.ott.GenerateOneTimeTokenWebFilter[] 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
 | |
| @EnableWebFluxSecurity
 | |
| public class SecurityConfig {
 | |
| 
 | |
|     @Bean
 | |
|     public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|         http
 | |
|             // ...
 | |
|             .formLogin(Customizer.withDefaults())
 | |
|             .oneTimeTokenLogin((ott) -> ott
 | |
|                 .generateTokenUrl("/ott/my-generate-url")
 | |
|             );
 | |
|         return http.build();
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| @Component
 | |
| public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Configuration
 | |
| @EnableWebFluxSecurity
 | |
| class SecurityConfig {
 | |
| 
 | |
|          open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|              return http {
 | |
|                  // ...
 | |
|                  formLogin { }
 | |
|                  oneTimeTokenLogin {
 | |
|                     generateTokenUrl = "/ott/my-generate-url"
 | |
|                  }
 | |
|              }
 | |
|          }
 | |
| 
 | |
| }
 | |
| 
 | |
| @Component
 | |
| class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | |
|     // ...
 | |
| }
 | |
| 
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[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.server.ui.OneTimeTokenSubmitPageGeneratingWebFilter[] 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
 | |
| @EnableWebFluxSecurity
 | |
| public class SecurityConfig {
 | |
| 
 | |
|     @Bean
 | |
|     public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|         http
 | |
|             // ...
 | |
|             .formLogin(Customizer.withDefaults())
 | |
|             .oneTimeTokenLogin((ott) -> ott
 | |
|                 .submitPageUrl("/ott/submit")
 | |
|             );
 | |
|         return http.build();
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| @Component
 | |
| public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Configuration
 | |
| @EnableWebFluxSecurity
 | |
| class SecurityConfig {
 | |
| 
 | |
|          open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|              return http {
 | |
|                  // ...
 | |
|                  formLogin { }
 | |
|                  oneTimeTokenLogin {
 | |
|                     submitPageUrl = "/ott/submit"
 | |
|                  }
 | |
|              }
 | |
|          }
 | |
| 
 | |
| }
 | |
| 
 | |
| @Component
 | |
| class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | |
|     // ...
 | |
| }
 | |
| 
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[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
 | |
| @EnableWebFluxSecurity
 | |
| public class SecurityConfig {
 | |
| 
 | |
|     @Bean
 | |
|     public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|         http
 | |
|             .authorizeExchange((authorize) -> authorize
 | |
|                 .pathMatchers("/my-ott-submit").permitAll()
 | |
|                 .anyExchange().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 MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Configuration
 | |
| @EnableWebFluxSecurity
 | |
| class SecurityConfig {
 | |
| 
 | |
|          open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|              return http {
 | |
|                 authorizeExchange {
 | |
|                     authorize(pathMatchers("/my-ott-submit"), permitAll)
 | |
|                     authorize(anyExchange, authenticated)
 | |
|                  }
 | |
|                  .formLogin { }
 | |
|                  oneTimeTokenLogin {
 | |
|                     showDefaultSubmitPage = false
 | |
|                  }
 | |
|              }
 | |
|          }
 | |
| 
 | |
| }
 | |
| 
 | |
| @Controller
 | |
| class MyController {
 | |
| 
 | |
|     @GetMapping("/my-ott-submit")
 | |
|     fun ottSubmitPage(): String {
 | |
|         return "my-ott-submit"
 | |
|     }
 | |
| }
 | |
| 
 | |
| @Component
 | |
| class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | |
|     // ...
 | |
| }
 | |
| 
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[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.reactive.ReactiveOneTimeTokenService[].
 | |
| Spring Security uses the javadoc:org.springframework.security.authentication.ott.reactive.InMemoryReactiveOneTimeTokenService[] as the default implementation of that interface, if none is provided.
 | |
| 
 | |
| Some of the most common reasons to customize the `ReactiveOneTimeTokenService` 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 `ReactiveOneTimeTokenService`.
 | |
| One option is to provide it as a bean, so it can be automatically be picked-up by the `oneTimeTokenLogin()` DSL:
 | |
| 
 | |
| .Passing the ReactiveOneTimeTokenService as a Bean
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Configuration
 | |
| @EnableWebFluxSecurity
 | |
| public class SecurityConfig {
 | |
| 
 | |
|     @Bean
 | |
|     public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|         http
 | |
|             // ...
 | |
|             .formLogin(Customizer.withDefaults())
 | |
|             .oneTimeTokenLogin(Customizer.withDefaults());
 | |
|         return http.build();
 | |
|     }
 | |
| 
 | |
|     @Bean
 | |
|     public ReactiveOneTimeTokenService oneTimeTokenService() {
 | |
|         return new MyCustomReactiveOneTimeTokenService();
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| @Component
 | |
| public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Configuration
 | |
| @EnableWebFluxSecurity
 | |
| class SecurityConfig {
 | |
| 
 | |
|          open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|              return http {
 | |
|                  //..
 | |
|                  .formLogin { }
 | |
|                  oneTimeTokenLogin { }
 | |
|              }
 | |
|          }
 | |
| 
 | |
|          @Bean
 | |
|          open fun oneTimeTokenService():ReactiveOneTimeTokenService {
 | |
|              return MyCustomReactiveOneTimeTokenService();
 | |
|          }
 | |
| 
 | |
| }
 | |
| 
 | |
| @Component
 | |
| class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | |
|     // ...
 | |
| }
 | |
| 
 | |
| ----
 | |
| ======
 | |
| 
 | |
| The second option is to pass the `ReactiveOneTimeTokenService` instance to the DSL, which is useful if there are multiple ``SecurityWebFilterChain``s and a different ``ReactiveOneTimeTokenService``s is needed for each of them.
 | |
| 
 | |
| .Passing the ReactiveOneTimeTokenService using the DSL
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Configuration
 | |
| @EnableWebFluxSecurity
 | |
| public class SecurityConfig {
 | |
| 
 | |
|     @Bean
 | |
|     public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|         http
 | |
|             // ...
 | |
|             .formLogin(Customizer.withDefaults())
 | |
|             .oneTimeTokenLogin((ott) -> ott
 | |
|                 .oneTimeTokenService(new MyCustomReactiveOneTimeTokenService())
 | |
|             );
 | |
|         return http.build();
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| @Component
 | |
| public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Configuration
 | |
| @EnableWebFluxSecurity
 | |
| class SecurityConfig {
 | |
| 
 | |
|          open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|              return http {
 | |
|                  //..
 | |
|                  .formLogin { }
 | |
|                  oneTimeTokenLogin {
 | |
|                     oneTimeTokenService = MyCustomReactiveOneTimeTokenService()
 | |
|                  }
 | |
|              }
 | |
|          }
 | |
| }
 | |
| 
 | |
| @Component
 | |
| class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | |
|     // ...
 | |
| }
 | |
| 
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[customize-generate-token-request]]
 | |
| == Customize GenerateOneTimeTokenRequest Instance
 | |
| There are a number of reasons that you may want to adjust an GenerateOneTimeTokenRequest. For example, you may want expiresIn to be set to 10 mins, which Spring Security sets to 5 mins by default.
 | |
| 
 | |
| You can customize elements of GenerateOneTimeTokenRequest by publishing an ServerGenerateOneTimeTokenRequestResolver as a @Bean, like so:
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| ServerGenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() {
 | |
|     DefaultServerGenerateOneTimeTokenRequestResolver resolver = new DefaultServerGenerateOneTimeTokenRequestResolver();
 | |
|     resolver.setExpiresIn(Duration.ofSeconds(600));
 | |
|     return resolver;
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Bean
 | |
| fun generateOneTimeTokenRequestResolver() : ServerGenerateOneTimeTokenRequestResolver {
 | |
|     return DefaultServerGenerateOneTimeTokenRequestResolver().apply {
 | |
|         this.setExpiresIn(Duration.ofMinutes(10))
 | |
|     }
 | |
| }
 | |
| ----
 | |
| ======
 |