Include code example for reactive onetimetoken
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
This commit is contained in:
committed by
Josh Cummings
parent
ff6cef0cd2
commit
27a76a67ae
@@ -53,138 +53,13 @@ Therefore, a custom javadoc:org.springframework.security.web.server.authenticati
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
======
|
||||
|
||||
include-code::./SecurityConfig[tag=config,indent=0]
|
||||
include-code::./MagicLinkOneTimeTokenGenerationSuccessHandler[tag=snippet,indent=0]
|
||||
<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
|
||||
<5> Use the `ServerOneTimeTokenGenerationSuccessHandler` to perform a redirect to your desired URL
|
||||
|
||||
The email content will look similar to:
|
||||
|
||||
@@ -196,65 +71,10 @@ The default submit page will detect that the URL has the `token` query param and
|
||||
== 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:
|
||||
That URL can be changed by using the `tokenGeneratingUrl(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 {
|
||||
// ...
|
||||
}
|
||||
|
||||
----
|
||||
======
|
||||
include-code::./SecurityConfig[tag=config,indent=0]
|
||||
include-code::./MagicLinkOneTimeTokenGenerationSuccessHandler[tag=snippet,indent=0]
|
||||
|
||||
[[changing-submit-page-url]]
|
||||
== Changing the Default Submit Page URL
|
||||
@@ -262,151 +82,17 @@ class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender):
|
||||
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 {
|
||||
// ...
|
||||
}
|
||||
|
||||
----
|
||||
======
|
||||
include-code::./SecurityConfig[tag=config,indent=0]
|
||||
include-code::./MagicLinkOneTimeTokenGenerationSuccessHandler[tag=snippet,indent=0]
|
||||
|
||||
[[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 {
|
||||
// ...
|
||||
}
|
||||
|
||||
----
|
||||
======
|
||||
include-code::./SecurityConfig[tag=config,indent=0]
|
||||
include-code::./MagicLinkOneTimeTokenGenerationSuccessHandler[tag=snippet,indent=0]
|
||||
include-code::./MyController[tag=snippet,indent=0]
|
||||
|
||||
[[customize-generate-consume-token]]
|
||||
== Customize How to Generate and Consume One-Time Tokens
|
||||
@@ -424,157 +110,16 @@ Some of the most common reasons to customize the `ReactiveOneTimeTokenService` a
|
||||
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 {
|
||||
// ...
|
||||
}
|
||||
|
||||
----
|
||||
======
|
||||
include-code::./OneTimeTokenServiceBeanSecurityConfig[tag=config,indent=0]
|
||||
include-code::./MagicLinkOneTimeTokenGenerationSuccessHandler[tag=snippet,indent=0]
|
||||
|
||||
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 {
|
||||
// ...
|
||||
}
|
||||
|
||||
----
|
||||
======
|
||||
include-code::./OneTimeTokenServiceDSLSecurityConfig[tag=config,indent=0]
|
||||
include-code::./MagicLinkOneTimeTokenGenerationSuccessHandler[tag=snippet,indent=0]
|
||||
|
||||
[[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))
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./SecurityConfig[tag=config,indent=0]
|
||||
|
||||
Reference in New Issue
Block a user