mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-25 21:42:17 +00:00
Polish One-Time Token API Names and Doc
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
This commit is contained in:
parent
e9fe6360bc
commit
d37d41c130
@ -3003,8 +3003,8 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @Bean
|
* @Bean
|
||||||
* public GeneratedOneTimeTokenHandler generatedOneTimeTokenHandler() {
|
* public OneTimeTokenGenerationSuccessHandler oneTimeTokenGenerationSuccessHandler() {
|
||||||
* return new MyMagicLinkGeneratedOneTimeTokenHandler();
|
* return new MyMagicLinkOneTimeTokenGenerationSuccessHandler();
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* }
|
* }
|
||||||
|
@ -133,19 +133,19 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
|
|
||||||
private void configureOttGenerateFilter(H http) {
|
private void configureOttGenerateFilter(H http) {
|
||||||
GenerateOneTimeTokenFilter generateFilter = new GenerateOneTimeTokenFilter(getOneTimeTokenService(http),
|
GenerateOneTimeTokenFilter generateFilter = new GenerateOneTimeTokenFilter(getOneTimeTokenService(http),
|
||||||
getGeneratedOneTimeTokenHandler(http));
|
getOneTimeTokenGenerationSuccessHandler(http));
|
||||||
generateFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.tokenGeneratingUrl));
|
generateFilter.setRequestMatcher(antMatcher(HttpMethod.POST, this.tokenGeneratingUrl));
|
||||||
http.addFilter(postProcess(generateFilter));
|
http.addFilter(postProcess(generateFilter));
|
||||||
http.addFilter(DefaultResourcesFilter.css());
|
http.addFilter(DefaultResourcesFilter.css());
|
||||||
}
|
}
|
||||||
|
|
||||||
private OneTimeTokenGenerationSuccessHandler getGeneratedOneTimeTokenHandler(H http) {
|
private OneTimeTokenGenerationSuccessHandler getOneTimeTokenGenerationSuccessHandler(H http) {
|
||||||
if (this.oneTimeTokenGenerationSuccessHandler == null) {
|
if (this.oneTimeTokenGenerationSuccessHandler == null) {
|
||||||
this.oneTimeTokenGenerationSuccessHandler = getBeanOrNull(http, OneTimeTokenGenerationSuccessHandler.class);
|
this.oneTimeTokenGenerationSuccessHandler = getBeanOrNull(http, OneTimeTokenGenerationSuccessHandler.class);
|
||||||
}
|
}
|
||||||
if (this.oneTimeTokenGenerationSuccessHandler == null) {
|
if (this.oneTimeTokenGenerationSuccessHandler == null) {
|
||||||
throw new IllegalStateException("""
|
throw new IllegalStateException("""
|
||||||
A GeneratedOneTimeTokenHandler is required to enable oneTimeTokenLogin().
|
A OneTimeTokenGenerationSuccessHandler is required to enable oneTimeTokenLogin().
|
||||||
Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
|
Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
@ -200,7 +200,7 @@ public final class OneTimeTokenLoginConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
*/
|
*/
|
||||||
public OneTimeTokenLoginConfigurer<H> tokenGenerationSuccessHandler(
|
public OneTimeTokenLoginConfigurer<H> tokenGenerationSuccessHandler(
|
||||||
OneTimeTokenGenerationSuccessHandler oneTimeTokenGenerationSuccessHandler) {
|
OneTimeTokenGenerationSuccessHandler oneTimeTokenGenerationSuccessHandler) {
|
||||||
Assert.notNull(oneTimeTokenGenerationSuccessHandler, "generatedOneTimeTokenHandler cannot be null");
|
Assert.notNull(oneTimeTokenGenerationSuccessHandler, "oneTimeTokenGenerationSuccessHandler cannot be null");
|
||||||
this.oneTimeTokenGenerationSuccessHandler = oneTimeTokenGenerationSuccessHandler;
|
this.oneTimeTokenGenerationSuccessHandler = oneTimeTokenGenerationSuccessHandler;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -1578,8 +1578,8 @@ public class ServerHttpSecurity {
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @Bean
|
* @Bean
|
||||||
* public ServerGeneratedOneTimeTokenHandler generatedOneTimeTokenHandler() {
|
* public ServerOneTimeTokenGenerationSuccessHandler oneTimeTokenGenerationSuccessHandler() {
|
||||||
* return new MyMagicLinkServerGeneratedOneTimeTokenHandler();
|
* return new MyMagicLinkServerOneTimeTokenGenerationSuccessHandler();
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* }
|
* }
|
||||||
@ -6151,12 +6151,12 @@ public class ServerHttpSecurity {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies strategy to be used to handle generated one-time tokens.
|
* Specifies strategy to be used to handle generated one-time tokens.
|
||||||
* @param generatedOneTimeTokenHandler
|
* @param oneTimeTokenGenerationSuccessHandler
|
||||||
*/
|
*/
|
||||||
public OneTimeTokenLoginSpec tokenGenerationSuccessHandler(
|
public OneTimeTokenLoginSpec tokenGenerationSuccessHandler(
|
||||||
ServerOneTimeTokenGenerationSuccessHandler generatedOneTimeTokenHandler) {
|
ServerOneTimeTokenGenerationSuccessHandler oneTimeTokenGenerationSuccessHandler) {
|
||||||
Assert.notNull(generatedOneTimeTokenHandler, "generatedOneTimeTokenHandler cannot be null");
|
Assert.notNull(oneTimeTokenGenerationSuccessHandler, "oneTimeTokenGenerationSuccessHandler cannot be null");
|
||||||
this.tokenGenerationSuccessHandler = generatedOneTimeTokenHandler;
|
this.tokenGenerationSuccessHandler = oneTimeTokenGenerationSuccessHandler;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6193,7 +6193,7 @@ public class ServerHttpSecurity {
|
|||||||
}
|
}
|
||||||
if (this.tokenGenerationSuccessHandler == null) {
|
if (this.tokenGenerationSuccessHandler == null) {
|
||||||
throw new IllegalStateException("""
|
throw new IllegalStateException("""
|
||||||
A ServerGeneratedOneTimeTokenHandler is required to enable oneTimeTokenLogin().
|
A ServerOneTimeTokenGenerationSuccessHandler is required to enable oneTimeTokenLogin().
|
||||||
Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
|
Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
@ -985,7 +985,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||||||
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
* http {
|
* http {
|
||||||
* oneTimeTokenLogin {
|
* oneTimeTokenLogin {
|
||||||
* generatedOneTimeTokenHandler = MyMagicLinkGeneratedOneTimeTokenHandler()
|
* oneTimeTokenGenerationSuccessHandler = MyMagicLinkOneTimeTokenGenerationSuccessHandler()
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* return http.build()
|
* return http.build()
|
||||||
|
@ -189,7 +189,7 @@ public class OneTimeTokenLoginConfigurerTests {
|
|||||||
.havingRootCause()
|
.havingRootCause()
|
||||||
.isInstanceOf(IllegalStateException.class)
|
.isInstanceOf(IllegalStateException.class)
|
||||||
.withMessage("""
|
.withMessage("""
|
||||||
A GeneratedOneTimeTokenHandler is required to enable oneTimeTokenLogin().
|
A OneTimeTokenGenerationSuccessHandler is required to enable oneTimeTokenLogin().
|
||||||
Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
|
Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
@ -269,13 +269,13 @@ public class OneTimeTokenLoginSpecTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void oneTimeTokenWhenNoGeneratedOneTimeTokenHandlerThenException() {
|
void oneTimeTokenWhenNoOneTimeTokenGenerationSuccessHandlerThenException() {
|
||||||
assertThatException()
|
assertThatException()
|
||||||
.isThrownBy(() -> this.spring.register(OneTimeTokenNotGeneratedOttHandlerConfig.class).autowire())
|
.isThrownBy(() -> this.spring.register(OneTimeTokenNotGeneratedOttHandlerConfig.class).autowire())
|
||||||
.havingRootCause()
|
.havingRootCause()
|
||||||
.isInstanceOf(IllegalStateException.class)
|
.isInstanceOf(IllegalStateException.class)
|
||||||
.withMessage("""
|
.withMessage("""
|
||||||
A ServerGeneratedOneTimeTokenHandler is required to enable oneTimeTokenLogin().
|
A ServerOneTimeTokenGenerationSuccessHandler is required to enable oneTimeTokenLogin().
|
||||||
Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
|
Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ class OneTimeTokenLoginDslTests {
|
|||||||
.redirectedUrl("/login/ott")
|
.redirectedUrl("/login/ott")
|
||||||
)
|
)
|
||||||
|
|
||||||
val token = TestGeneratedOneTimeTokenHandler.lastToken?.tokenValue
|
val token = TestOneTimeTokenGenerationSuccessHandler.lastToken?.tokenValue
|
||||||
|
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
MockMvcRequestBuilders.post("/login/ott").param("token", token)
|
MockMvcRequestBuilders.post("/login/ott").param("token", token)
|
||||||
@ -91,7 +91,7 @@ class OneTimeTokenLoginDslTests {
|
|||||||
)
|
)
|
||||||
.andExpectAll(MockMvcResultMatchers.status().isFound(), MockMvcResultMatchers.redirectedUrl("/redirected"))
|
.andExpectAll(MockMvcResultMatchers.status().isFound(), MockMvcResultMatchers.redirectedUrl("/redirected"))
|
||||||
|
|
||||||
val token = TestGeneratedOneTimeTokenHandler.lastToken?.tokenValue
|
val token = TestOneTimeTokenGenerationSuccessHandler.lastToken?.tokenValue
|
||||||
|
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
MockMvcRequestBuilders.post("/loginprocessingurl").param("token", token)
|
MockMvcRequestBuilders.post("/loginprocessingurl").param("token", token)
|
||||||
@ -117,7 +117,7 @@ class OneTimeTokenLoginDslTests {
|
|||||||
authorize(anyRequest, authenticated)
|
authorize(anyRequest, authenticated)
|
||||||
}
|
}
|
||||||
oneTimeTokenLogin {
|
oneTimeTokenLogin {
|
||||||
oneTimeTokenGenerationSuccessHandler = TestGeneratedOneTimeTokenHandler()
|
oneTimeTokenGenerationSuccessHandler = TestOneTimeTokenGenerationSuccessHandler()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
@ -138,7 +138,7 @@ class OneTimeTokenLoginDslTests {
|
|||||||
}
|
}
|
||||||
oneTimeTokenLogin {
|
oneTimeTokenLogin {
|
||||||
tokenGeneratingUrl = "/generateurl"
|
tokenGeneratingUrl = "/generateurl"
|
||||||
oneTimeTokenGenerationSuccessHandler = TestGeneratedOneTimeTokenHandler("/redirected")
|
oneTimeTokenGenerationSuccessHandler = TestOneTimeTokenGenerationSuccessHandler("/redirected")
|
||||||
loginProcessingUrl = "/loginprocessingurl"
|
loginProcessingUrl = "/loginprocessingurl"
|
||||||
authenticationSuccessHandler = SimpleUrlAuthenticationSuccessHandler("/authenticated")
|
authenticationSuccessHandler = SimpleUrlAuthenticationSuccessHandler("/authenticated")
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ class OneTimeTokenLoginDslTests {
|
|||||||
InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin())
|
InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin())
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestGeneratedOneTimeTokenHandler :
|
private class TestOneTimeTokenGenerationSuccessHandler :
|
||||||
OneTimeTokenGenerationSuccessHandler {
|
OneTimeTokenGenerationSuccessHandler {
|
||||||
private val delegate: OneTimeTokenGenerationSuccessHandler
|
private val delegate: OneTimeTokenGenerationSuccessHandler
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ Java::
|
|||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityWebFilterChain filterChain(ServerHttpSecurity http, MagicLinkGeneratedOneTimeTokenHandler magicLinkSender) {
|
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.formLogin(Customizer.withDefaults())
|
.formLogin(Customizer.withDefaults())
|
||||||
@ -79,11 +79,11 @@ import org.springframework.mail.SimpleMailMessage;
|
|||||||
import org.springframework.mail.javamail.JavaMailSender;
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
|
||||||
@Component <1>
|
@Component <1>
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
|
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
|
||||||
|
|
||||||
private final MailSender mailSender;
|
private final MailSender mailSender;
|
||||||
|
|
||||||
private final ServerGeneratedOneTimeTokenHandler redirectHandler = new ServerRedirectGeneratedOneTimeTokenHandler("/ott/sent");
|
private final ServerOneTimeTokenGenerationSuccessHandler redirectHandler = new ServerRedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
|
||||||
|
|
||||||
// constructor omitted
|
// constructor omitted
|
||||||
|
|
||||||
@ -119,14 +119,72 @@ class PageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
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 `MagicLinkGeneratedOneTimeTokenHandler` a Spring bean
|
<1> Make the `MagicLinkOneTimeTokenGenerationSuccessHandler` a Spring bean
|
||||||
<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 `MailSender` API to send the email to the user with the magic link
|
||||||
<5> Use the `ServerRedirectOneTimeTokenGenerationSuccessHandler` to perform a redirect to your desired URL
|
<5> Use the `ServerRedirectStrategy` to perform a redirect to your desired URL
|
||||||
|
|
||||||
The email content will look similar to:
|
The email content will look similar to:
|
||||||
|
|
||||||
@ -165,9 +223,36 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
|
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 {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
@ -202,9 +287,36 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
|
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 {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
@ -251,9 +363,48 @@ public class MyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
|
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 {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
@ -301,9 +452,39 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
|
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 {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
@ -334,8 +515,34 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
|
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 {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
@ -65,7 +65,7 @@ Java::
|
|||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http, MagicLinkGeneratedOneTimeTokenHandler magicLinkSender) {
|
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.formLogin(Customizer.withDefaults())
|
.formLogin(Customizer.withDefaults())
|
||||||
@ -79,11 +79,11 @@ import org.springframework.mail.SimpleMailMessage;
|
|||||||
import org.springframework.mail.javamail.JavaMailSender;
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
|
||||||
@Component <1>
|
@Component <1>
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements GeneratedOneTimeTokenSuccessHandler {
|
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||||
|
|
||||||
private final MailSender mailSender;
|
private final MailSender mailSender;
|
||||||
|
|
||||||
private final GeneratedOneTimeTokenHandler redirectHandler = new RedirectGeneratedOneTimeTokenHandler("/ott/sent");
|
private final OneTimeTokenGenerationSuccessHandler redirectHandler = new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
|
||||||
|
|
||||||
// constructor omitted
|
// constructor omitted
|
||||||
|
|
||||||
@ -128,10 +128,7 @@ Kotlin::
|
|||||||
class SecurityConfig {
|
class SecurityConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
open fun filterChain(
|
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
http: HttpSecurity,
|
|
||||||
magicLinkSender: MagicLinkGeneratedOneTimeTokenSuccessHandler?
|
|
||||||
): SecurityFilterChain {
|
|
||||||
http{
|
http{
|
||||||
formLogin {}
|
formLogin {}
|
||||||
oneTimeTokenLogin { }
|
oneTimeTokenLogin { }
|
||||||
@ -144,10 +141,10 @@ import org.springframework.mail.SimpleMailMessage;
|
|||||||
import org.springframework.mail.javamail.JavaMailSender;
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
|
||||||
@Component (1)
|
@Component (1)
|
||||||
class MagicLinkGeneratedOneTimeTokenSuccessHandler(
|
class MagicLinkOneTimeTokenGenerationSuccessHandler(
|
||||||
private val mailSender: MailSender,
|
private val mailSender: MailSender,
|
||||||
private val redirectHandler: GeneratedOneTimeTokenHandler = RedirectGeneratedOneTimeTokenHandler("/ott/sent")
|
private val redirectHandler: OneTimeTokenGenerationSuccessHandler = RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent")
|
||||||
) : GeneratedOneTimeTokenHandler {
|
) : OneTimeTokenGenerationSuccessHandler {
|
||||||
|
|
||||||
override fun handle(request: HttpServletRequest, response: HttpServletResponse, oneTimeToken: OneTimeToken) {
|
override fun handle(request: HttpServletRequest, response: HttpServletResponse, oneTimeToken: OneTimeToken) {
|
||||||
val builder = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
val builder = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||||
@ -179,7 +176,7 @@ class PageController {
|
|||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
<1> Make the `MagicLinkGeneratedOneTimeTokenHandler` a Spring bean
|
<1> Make the `MagicLinkOneTimeTokenGenerationSuccessHandler` a Spring bean
|
||||||
<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
|
||||||
@ -222,7 +219,7 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements GeneratedOneTimeTokenSuccessHandler {
|
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -249,7 +246,7 @@ class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class MagicLinkGeneratedOneTimeTokenSuccessHandler : GeneratedOneTimeTokenHandler {
|
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -286,7 +283,7 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements GeneratedOneTimeTokenSuccessHandler {
|
public class MagicLinkGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -313,7 +310,7 @@ class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class MagicLinkGeneratedOneTimeTokenSuccessHandler : GeneratedOneTimeTokenHandler {
|
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -362,7 +359,7 @@ public class MyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements GeneratedOneTimeTokenSuccessHandler {
|
public class OneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -401,7 +398,7 @@ class MyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class MagicLinkGeneratedOneTimeTokenSuccessHandler : GeneratedOneTimeTokenHandler {
|
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -452,7 +449,7 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements GeneratedOneTimeTokenSuccessHandler {
|
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -482,7 +479,7 @@ class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class MagicLinkGeneratedOneTimeTokenSuccessHandler : GeneratedOneTimeTokenHandler {
|
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -515,7 +512,7 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MagicLinkGeneratedOneTimeTokenHandler implements GeneratedOneTimeTokenSuccessHandler {
|
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -543,7 +540,7 @@ class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class MagicLinkGeneratedOneTimeTokenSuccessHandler : GeneratedOneTimeTokenHandler {
|
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
@ -43,13 +43,13 @@ public final class GenerateOneTimeTokenWebFilter implements WebFilter {
|
|||||||
|
|
||||||
private ServerWebExchangeMatcher matcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/ott/generate");
|
private ServerWebExchangeMatcher matcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/ott/generate");
|
||||||
|
|
||||||
private final ServerOneTimeTokenGenerationSuccessHandler generatedOneTimeTokenHandler;
|
private final ServerOneTimeTokenGenerationSuccessHandler oneTimeTokenGenerationSuccessHandler;
|
||||||
|
|
||||||
public GenerateOneTimeTokenWebFilter(ReactiveOneTimeTokenService oneTimeTokenService,
|
public GenerateOneTimeTokenWebFilter(ReactiveOneTimeTokenService oneTimeTokenService,
|
||||||
ServerOneTimeTokenGenerationSuccessHandler generatedOneTimeTokenHandler) {
|
ServerOneTimeTokenGenerationSuccessHandler oneTimeTokenGenerationSuccessHandler) {
|
||||||
Assert.notNull(generatedOneTimeTokenHandler, "generatedOneTimeTokenHandler cannot be null");
|
Assert.notNull(oneTimeTokenGenerationSuccessHandler, "oneTimeTokenGenerationSuccessHandler cannot be null");
|
||||||
Assert.notNull(oneTimeTokenService, "oneTimeTokenService cannot be null");
|
Assert.notNull(oneTimeTokenService, "oneTimeTokenService cannot be null");
|
||||||
this.generatedOneTimeTokenHandler = generatedOneTimeTokenHandler;
|
this.oneTimeTokenGenerationSuccessHandler = oneTimeTokenGenerationSuccessHandler;
|
||||||
this.oneTimeTokenService = oneTimeTokenService;
|
this.oneTimeTokenService = oneTimeTokenService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ public final class GenerateOneTimeTokenWebFilter implements WebFilter {
|
|||||||
.mapNotNull((data) -> data.getFirst(USERNAME))
|
.mapNotNull((data) -> data.getFirst(USERNAME))
|
||||||
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
|
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
|
||||||
.flatMap((username) -> this.oneTimeTokenService.generate(new GenerateOneTimeTokenRequest(username)))
|
.flatMap((username) -> this.oneTimeTokenService.generate(new GenerateOneTimeTokenRequest(username)))
|
||||||
.flatMap((token) -> this.generatedOneTimeTokenHandler.handle(exchange, token));
|
.flatMap((token) -> this.oneTimeTokenGenerationSuccessHandler.handle(exchange, token));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ public class GenerateOneTimeTokenWebFilterTests {
|
|||||||
|
|
||||||
private final ReactiveOneTimeTokenService oneTimeTokenService = mock(ReactiveOneTimeTokenService.class);
|
private final ReactiveOneTimeTokenService oneTimeTokenService = mock(ReactiveOneTimeTokenService.class);
|
||||||
|
|
||||||
private final ServerRedirectOneTimeTokenGenerationSuccessHandler generatedOneTimeTokenHandler = new ServerRedirectOneTimeTokenGenerationSuccessHandler(
|
private final ServerRedirectOneTimeTokenGenerationSuccessHandler oneTimeTokenGenerationSuccessHandler = new ServerRedirectOneTimeTokenGenerationSuccessHandler(
|
||||||
"/login/ott");
|
"/login/ott");
|
||||||
|
|
||||||
private static final String TOKEN = "token";
|
private static final String TOKEN = "token";
|
||||||
@ -60,7 +60,7 @@ public class GenerateOneTimeTokenWebFilterTests {
|
|||||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
.body("username=user"));
|
.body("username=user"));
|
||||||
GenerateOneTimeTokenWebFilter filter = new GenerateOneTimeTokenWebFilter(this.oneTimeTokenService,
|
GenerateOneTimeTokenWebFilter filter = new GenerateOneTimeTokenWebFilter(this.oneTimeTokenService,
|
||||||
this.generatedOneTimeTokenHandler);
|
this.oneTimeTokenGenerationSuccessHandler);
|
||||||
|
|
||||||
filter.filter(exchange, (e) -> Mono.empty()).block();
|
filter.filter(exchange, (e) -> Mono.empty()).block();
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ public class GenerateOneTimeTokenWebFilterTests {
|
|||||||
MockServerHttpRequest.BaseBuilder<?> request = MockServerHttpRequest.post("/ott/generate");
|
MockServerHttpRequest.BaseBuilder<?> request = MockServerHttpRequest.post("/ott/generate");
|
||||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
GenerateOneTimeTokenWebFilter filter = new GenerateOneTimeTokenWebFilter(this.oneTimeTokenService,
|
GenerateOneTimeTokenWebFilter filter = new GenerateOneTimeTokenWebFilter(this.oneTimeTokenService,
|
||||||
this.generatedOneTimeTokenHandler);
|
this.oneTimeTokenGenerationSuccessHandler);
|
||||||
|
|
||||||
filter.filter(exchange, (e) -> Mono.empty()).block();
|
filter.filter(exchange, (e) -> Mono.empty()).block();
|
||||||
|
|
||||||
@ -86,14 +86,14 @@ public class GenerateOneTimeTokenWebFilterTests {
|
|||||||
public void constructorWhenOneTimeTokenServiceNullThenIllegalArgumentException() {
|
public void constructorWhenOneTimeTokenServiceNullThenIllegalArgumentException() {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
assertThatIllegalArgumentException()
|
assertThatIllegalArgumentException()
|
||||||
.isThrownBy(() -> new GenerateOneTimeTokenWebFilter(null, this.generatedOneTimeTokenHandler));
|
.isThrownBy(() -> new GenerateOneTimeTokenWebFilter(null, this.oneTimeTokenGenerationSuccessHandler));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setWhenRequestMatcherNullThenIllegalArgumentException() {
|
public void setWhenRequestMatcherNullThenIllegalArgumentException() {
|
||||||
GenerateOneTimeTokenWebFilter filter = new GenerateOneTimeTokenWebFilter(this.oneTimeTokenService,
|
GenerateOneTimeTokenWebFilter filter = new GenerateOneTimeTokenWebFilter(this.oneTimeTokenService,
|
||||||
this.generatedOneTimeTokenHandler);
|
this.oneTimeTokenGenerationSuccessHandler);
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
assertThatIllegalArgumentException()
|
assertThatIllegalArgumentException()
|
||||||
.isThrownBy(() -> filter.setRequestMatcher(null));
|
.isThrownBy(() -> filter.setRequestMatcher(null));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user