Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 137d2e81b7 | |||
| 8525cbfd19 | |||
| 02f5f5a459 | |||
| 3dff446d18 | |||
| cc89707b03 | |||
| 812ad9c547 | |||
| 364c153e17 | |||
| 39383ab970 | |||
| 8ef5bcd003 | |||
| 4193b73d47 | |||
| 4ce48297f6 | |||
| 9989735a49 | |||
| 02cc2e9d14 | |||
| 118e51d45e | |||
| 27a76a67ae | |||
| ff6cef0cd2 | |||
| 801f890ed0 | |||
| 1a7769be28 | |||
| bdf501fe87 | |||
| a161421409 | |||
| 978eb4396a | |||
| 770c860d9d |
@@ -1,17 +0,0 @@
|
||||
name: Merge Dependabot PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- '*.x'
|
||||
- 'docs-build'
|
||||
|
||||
run-name: Merge Dependabot PR ${{ github.ref_name }}
|
||||
|
||||
jobs:
|
||||
merge-dependabot-pr:
|
||||
permissions: write-all
|
||||
uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@0d3f15bb384839966a1ff5c4383731a2b747f24b # v7
|
||||
with:
|
||||
mergeArguments: --auto --rebase
|
||||
@@ -17,7 +17,7 @@ permissions:
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@3f6cc7ffc137ca160061749d5f34dc30d5f36986 # v1.0.17
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: spring-io/spring-release-actions/compute-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
|
||||
- name: Get Today's Release Version
|
||||
id: todays-release
|
||||
uses: spring-io/spring-release-actions/get-todays-release-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
|
||||
uses: spring-io/spring-release-actions/get-todays-release-version@1b8671612c3eb3d9b9763e2d7b66f1a80d00ee95 # 0.0.6
|
||||
with:
|
||||
snapshot-version: ${{ steps.compute-version.outputs.version }}
|
||||
milestone-repository: ${{ github.repository }}
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
||||
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
|
||||
with:
|
||||
java-version: '25'
|
||||
distribution: 'temurin'
|
||||
|
||||
+1
-1
@@ -123,7 +123,7 @@ public final class PostFilterAuthorizationMethodInterceptor implements Authoriza
|
||||
/**
|
||||
* Filter a {@code returnedObject} using the {@link PostFilter} annotation that the
|
||||
* {@link MethodInvocation} specifies.
|
||||
* @param mi the {@link MethodInvocation} to check check
|
||||
* @param mi the {@link MethodInvocation} to check
|
||||
* @return filtered {@code returnedObject}
|
||||
*/
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ public final class DelegatingSecurityContextCallable<V> implements Callable<V> {
|
||||
/**
|
||||
* Creates a {@link DelegatingSecurityContextCallable} and with the given
|
||||
* {@link Callable} and {@link SecurityContext}, but if the securityContext is null
|
||||
* will defaults to the current {@link SecurityContext} on the
|
||||
* will default to the current {@link SecurityContext} on the
|
||||
* {@link SecurityContextHolder}
|
||||
* @param delegate the delegate {@link DelegatingSecurityContextCallable} to run with
|
||||
* the specified {@link SecurityContext}. Cannot be null.
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ public final class DelegatingSecurityContextRunnable implements Runnable {
|
||||
.getContextHolderStrategy();
|
||||
|
||||
/**
|
||||
* The {@link SecurityContext} that the delegate {@link Runnable} will be ran as.
|
||||
* The {@link SecurityContext} that the delegate {@link Runnable} will be run as.
|
||||
*/
|
||||
private SecurityContext delegateSecurityContext;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.jspecify.annotations.Nullable;
|
||||
* building block for more sophisticated token-based solutions. For example,
|
||||
* authentication systems that depend on stateless session keys. These could, for
|
||||
* instance, place the username inside the user-specified extended information associated
|
||||
* with the key). It is important to recognise that we do not intend for this interface to
|
||||
* with the key. It is important to recognise that we do not intend for this interface to
|
||||
* be expanded to provide such capabilities directly.
|
||||
* </p>
|
||||
*
|
||||
|
||||
@@ -323,7 +323,7 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
|
||||
/**
|
||||
* Builds the user to be added. At minimum the username, password, and authorities
|
||||
* should provided. The remaining attributes have reasonable defaults.
|
||||
* should be provided. The remaining attributes have reasonable defaults.
|
||||
*/
|
||||
public static final class UserBuilder {
|
||||
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ public interface UserDetailsService {
|
||||
* may possibly be case sensitive, or case insensitive depending on how the
|
||||
* implementation instance is configured. In this case, the <code>UserDetails</code>
|
||||
* object that comes back may have a username that is of a different case than what
|
||||
* was actually requested..
|
||||
* was actually requested.
|
||||
* @param username the username identifying the user whose data is required.
|
||||
* @return a fully populated user record (never <code>null</code>)
|
||||
* @throws UsernameNotFoundException if the user could not be found or the user has no
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
/**
|
||||
* Contains simple user and authority group account provisioning interfaces together with
|
||||
* a a JDBC-based implementation.
|
||||
* a JDBC-based implementation.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.security.provisioning;
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ public class DelegatingSecurityContextAsyncTaskExecutor extends DelegatingSecuri
|
||||
}
|
||||
|
||||
@Override
|
||||
public final <T> Future<T> submit(Callable<T> task) {
|
||||
public final <T extends @Nullable Object> Future<T> submit(Callable<T> task) {
|
||||
return getDelegate().submit(wrap(task));
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -53,133 +53,12 @@ Therefore, a custom javadoc:org.springframework.security.web.authentication.ott.
|
||||
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.fromUriString(request.getRequestURL().toString())
|
||||
.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.fromUriString(request.getRequestURL().toString())
|
||||
.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"
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
======
|
||||
|
||||
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 `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 `RedirectOneTimeTokenGenerationSuccessHandler` to perform a redirect to your desired URL
|
||||
|
||||
The email content will look similar to:
|
||||
@@ -192,70 +71,15 @@ 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.authentication.ott.GenerateOneTimeTokenFilter[] 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
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin((ott) -> ott
|
||||
.tokenGeneratingUrl("/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 {
|
||||
tokenGeneratingUrl = "/ott/my-generate-url"
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./SecurityConfig[tag=config,indent=0]
|
||||
include-code::./MagicLinkOneTimeTokenGenerationSuccessHandler[tag=snippet,indent=0]
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The URI passed to `generateTokenUrl` is matched literally.
|
||||
If you want it to live under your application servlet's base path, include that prefix explicitly — for example, `generateTokenUrl("/api/ott/generate")`.
|
||||
The URI passed to `tokenGeneratingUrl` is matched literally.
|
||||
If you want it to live under your application servlet's base path, include that prefix explicitly — for example, `tokenGeneratingUrl("/api/ott/generate")`.
|
||||
The same applies to `loginProcessingUrl` and `tokenGeneratingUrl` elsewhere on this page.
|
||||
====
|
||||
|
||||
@@ -265,151 +89,17 @@ The same applies to `loginProcessingUrl` and `tokenGeneratingUrl` elsewhere on t
|
||||
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
|
||||
.defaultSubmitPageUrl("/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 {
|
||||
defaultSubmitPageUrl = "/ott/submit"
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
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
|
||||
@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 {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
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
|
||||
@@ -428,161 +118,18 @@ Some of the most common reasons to customize the `OneTimeTokenService` are, but
|
||||
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 {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./OneTimeTokenServiceBeanSecurityConfig[tag=config,indent=0]
|
||||
include-code::./MagicLinkOneTimeTokenGenerationSuccessHandler[tag=snippet,indent=0]
|
||||
|
||||
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 {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
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 GenerateOneTimeTokenRequestResolver as a @Bean, like so:
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() {
|
||||
DefaultGenerateOneTimeTokenRequestResolver delegate = new DefaultGenerateOneTimeTokenRequestResolver();
|
||||
return (request) -> {
|
||||
GenerateOneTimeTokenRequest generate = delegate.resolve(request);
|
||||
return new GenerateOneTimeTokenRequest(generate.getUsername(), Duration.ofSeconds(600));
|
||||
};
|
||||
}
|
||||
----
|
||||
You can customize elements of GenerateOneTimeTokenRequest by publishing an GenerateOneTimeTokenRequestResolver as a `@Bean`, like so:
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun generateRequestResolver() : GenerateOneTimeTokenRequestResolver {
|
||||
return DefaultGenerateOneTimeTokenRequestResolver().apply {
|
||||
this.setExpiresIn(Duration.ofMinutes(10))
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./SecurityConfig[tag=config,indent=0]
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"antora": "3.2.0-rc.2",
|
||||
"antora": "3.2.0-rc.3",
|
||||
"@antora/atlas-extension": "1.0.0-alpha.5",
|
||||
"@antora/collector-extension": "1.0.3",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
|
||||
@@ -49,6 +49,7 @@ dependencies {
|
||||
testImplementation 'org.springframework:spring-websocket'
|
||||
|
||||
testImplementation 'org.springframework:spring-webmvc'
|
||||
testImplementation 'org.springframework:spring-context-support'
|
||||
testImplementation 'jakarta.servlet:jakarta.servlet-api'
|
||||
testImplementation 'io.mockk:mockk'
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api"
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.changinggenerateurl;
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, OneTimeToken oneTimeToken) {
|
||||
/**/ return Mono.empty();
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.changinggenerateurl;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin((ott) -> ott
|
||||
.tokenGeneratingUrl("/ott/my-generate-url")
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.changingsubmitpageurl;
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, OneTimeToken oneTimeToken) {
|
||||
/**/ return Mono.empty();
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.changingsubmitpageurl;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin((ott) -> ott
|
||||
.defaultSubmitPageUrl("/ott/submit")
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.customizegenerateconsumetoken;
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, OneTimeToken oneTimeToken) {
|
||||
/**/ return Mono.empty();
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.customizegenerateconsumetoken;
|
||||
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken;
|
||||
import org.springframework.security.authentication.ott.reactive.ReactiveOneTimeTokenService;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
class MyCustomReactiveOneTimeTokenService implements ReactiveOneTimeTokenService {
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<OneTimeToken> generate(GenerateOneTimeTokenRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<OneTimeToken> consume(OneTimeTokenAuthenticationToken authenticationToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.customizegenerateconsumetoken;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.ott.reactive.ReactiveOneTimeTokenService;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.kt.docs.reactive.authentication.customizegenerateconsumetoken.MyCustomReactiveOneTimeTokenService;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class OneTimeTokenServiceBeanSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReactiveOneTimeTokenService oneTimeTokenService() {
|
||||
return new MyCustomReactiveOneTimeTokenService();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.customizegenerateconsumetoken;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class OneTimeTokenServiceDSLSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin((ott) -> ott
|
||||
.tokenService(new MyCustomReactiveOneTimeTokenService())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.customizegeneratetokenrequest;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.web.server.authentication.ott.DefaultServerGenerateOneTimeTokenRequestResolver;
|
||||
import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver;
|
||||
|
||||
public class SecurityConfig {
|
||||
|
||||
// tag::config[]
|
||||
@Bean
|
||||
ServerGenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() {
|
||||
DefaultServerGenerateOneTimeTokenRequestResolver resolver = new DefaultServerGenerateOneTimeTokenRequestResolver();
|
||||
resolver.setExpiresIn(Duration.ofMinutes(10));
|
||||
return resolver;
|
||||
}
|
||||
// end::config[]
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.disablingdefaultsubmitpage;
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, OneTimeToken oneTimeToken) {
|
||||
/**/ return Mono.empty();
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.disablingdefaultsubmitpage;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
// tag::snippet[]
|
||||
@Controller
|
||||
public class MyController {
|
||||
|
||||
@GetMapping("/my-ott-submit")
|
||||
public String ottSubmitPage() {
|
||||
return "my-ott-submit";
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.disablingdefaultsubmitpage;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.sendingtokentouser;
|
||||
|
||||
import org.springframework.mail.MailSender;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.security.web.server.authentication.ott.ServerRedirectOneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
// tag::snippet[]
|
||||
@Component // <1>
|
||||
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
private final MailSender mailSender;
|
||||
|
||||
private final ServerOneTimeTokenGenerationSuccessHandler redirectHandler = new ServerRedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
|
||||
|
||||
public MagicLinkOneTimeTokenGenerationSuccessHandler(MailSender mailSender) {
|
||||
this.mailSender = mailSender;
|
||||
}
|
||||
|
||||
@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) -> {
|
||||
|
||||
String email = getUserEmail(oneTimeToken.getUsername()); // <3>
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setTo(email);
|
||||
message.setSubject("Your Spring Security One Time Token");
|
||||
message.setText("Use the following link to sign in into the application: " + uri);
|
||||
this.mailSender.send(message); // <4>
|
||||
return Mono.empty();
|
||||
})
|
||||
.then(this.redirectHandler.handle(exchange, oneTimeToken)); // <5>
|
||||
}
|
||||
|
||||
private String getUserEmail(String username) {
|
||||
/**/ return username;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Controller
|
||||
class PageController {
|
||||
|
||||
@GetMapping("/ott/sent")
|
||||
String ottSent() {
|
||||
return "my-template";
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.reactive.authentication.sendingtokentouser;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.changinggenerateurl;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.changinggenerateurl;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin((ott) -> ott
|
||||
.tokenGeneratingUrl("/ott/my-generate-url")
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.changingsubmitpageurl;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.changingsubmitpageurl;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin((ott) -> ott
|
||||
.defaultSubmitPageUrl("/ott/submit")
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.customizegenerateconsumetoken;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.customizegenerateconsumetoken;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenService;
|
||||
|
||||
class MyCustomOneTimeTokenService implements OneTimeTokenService {
|
||||
|
||||
@Override
|
||||
public OneTimeToken generate(GenerateOneTimeTokenRequest request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable OneTimeToken consume(OneTimeTokenAuthenticationToken authenticationToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.customizegenerateconsumetoken;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenService;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class OneTimeTokenServiceBeanSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OneTimeTokenService oneTimeTokenService() {
|
||||
return new MyCustomOneTimeTokenService();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.customizegenerateconsumetoken;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class OneTimeTokenServiceDSLSecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin((ott) -> ott
|
||||
.tokenService(new MyCustomOneTimeTokenService())
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.customizegeneratetokenrequest;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
|
||||
import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver;
|
||||
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public class SecurityConfig {
|
||||
|
||||
// tag::config[]
|
||||
@Bean
|
||||
GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() {
|
||||
DefaultGenerateOneTimeTokenRequestResolver delegate = new DefaultGenerateOneTimeTokenRequestResolver();
|
||||
return (request) -> {
|
||||
GenerateOneTimeTokenRequest generate = delegate.resolve(request);
|
||||
return new GenerateOneTimeTokenRequest(generate.getUsername(), Duration.ofMinutes(10));
|
||||
};
|
||||
}
|
||||
// end::config[]
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.disablingdefaultsubmitpage;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.disablingdefaultsubmitpage;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
// tag::snippet[]
|
||||
@Controller
|
||||
public class MyController {
|
||||
|
||||
@GetMapping("/my-ott-submit")
|
||||
public String ottSubmitPage() {
|
||||
return "my-ott-submit";
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.disablingdefaultsubmitpage;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.sendingtokentouser;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.mail.MailSender;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
// tag::snippet[]
|
||||
@Component // <1>
|
||||
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
private final MailSender mailSender;
|
||||
|
||||
private final OneTimeTokenGenerationSuccessHandler redirectHandler = new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
|
||||
|
||||
public MagicLinkOneTimeTokenGenerationSuccessHandler(MailSender mailSender) {
|
||||
this.mailSender = mailSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) throws IOException, ServletException {
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(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>
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setTo(email);
|
||||
message.setSubject("Your Spring Security One Time Token");
|
||||
message.setText("Use the following link to sign in into the application: " + magicLink);
|
||||
this.mailSender.send(message); // <4>
|
||||
this.redirectHandler.handle(request, response, oneTimeToken); // <5>
|
||||
}
|
||||
|
||||
private String getUserEmail(String username) {
|
||||
/**/ return username;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Controller
|
||||
class PageController {
|
||||
|
||||
@GetMapping("/ott/sent")
|
||||
String ottSent() {
|
||||
return "my-template";
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.authentication.sendingtokentouser;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.oneTimeTokenLogin(Customizer.withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.changinggenerateurl
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler : ServerOneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
override fun handle(exchange: ServerWebExchange, oneTimeToken: OneTimeToken): Mono<Void> {
|
||||
/**/ return Mono.empty()
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.changinggenerateurl
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
open class SecurityConfig {
|
||||
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
formLogin { }
|
||||
oneTimeTokenLogin {
|
||||
tokenGeneratingUrl = "/ott/my-generate-url"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.changingsubmitpageurl
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler : ServerOneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
override fun handle(exchange: ServerWebExchange, oneTimeToken: OneTimeToken): Mono<Void> {
|
||||
/**/ return Mono.empty()
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.changingsubmitpageurl
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
open class SecurityConfig {
|
||||
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
formLogin { }
|
||||
oneTimeTokenLogin {
|
||||
defaultSubmitPageUrl = "/ott/submit"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.customizegenerateconsumetoken
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler : ServerOneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
override fun handle(exchange: ServerWebExchange, oneTimeToken: OneTimeToken): Mono<Void> {
|
||||
/**/ return Mono.empty()
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.customizegenerateconsumetoken
|
||||
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken
|
||||
import org.springframework.security.authentication.ott.reactive.ReactiveOneTimeTokenService
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
class MyCustomReactiveOneTimeTokenService: ReactiveOneTimeTokenService {
|
||||
|
||||
override fun generate(request: GenerateOneTimeTokenRequest): Mono<OneTimeToken> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun consume(authenticationToken: OneTimeTokenAuthenticationToken): Mono<OneTimeToken> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.customizegenerateconsumetoken
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.ott.reactive.ReactiveOneTimeTokenService
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
open class OneTimeTokenServiceBeanSecurityConfig {
|
||||
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
//..
|
||||
formLogin { }
|
||||
oneTimeTokenLogin { }
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun oneTimeTokenService(): ReactiveOneTimeTokenService {
|
||||
return MyCustomReactiveOneTimeTokenService()
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.customizegenerateconsumetoken
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
open class OneTimeTokenServiceDSLSecurityConfig {
|
||||
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
//..
|
||||
formLogin { }
|
||||
oneTimeTokenLogin {
|
||||
tokenService = MyCustomReactiveOneTimeTokenService()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.customizegeneratetokenrequest
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.web.server.authentication.ott.DefaultServerGenerateOneTimeTokenRequestResolver
|
||||
import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver
|
||||
import java.time.Duration
|
||||
|
||||
open class SecurityConfig {
|
||||
|
||||
// tag::config[]
|
||||
@Bean
|
||||
fun generateOneTimeTokenRequestResolver() : ServerGenerateOneTimeTokenRequestResolver {
|
||||
return DefaultServerGenerateOneTimeTokenRequestResolver().apply {
|
||||
this.setExpiresIn(Duration.ofMinutes(10))
|
||||
}
|
||||
}
|
||||
// end::config[]
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.disablingdefaultsubmitpage
|
||||
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler : ServerOneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
override fun handle(exchange: ServerWebExchange, oneTimeToken: OneTimeToken): Mono<Void> {
|
||||
/**/ return Mono.empty()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// end::snippet[]
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.disablingdefaultsubmitpage
|
||||
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
|
||||
// tag::snippet[]
|
||||
@Controller
|
||||
class MyController {
|
||||
|
||||
@GetMapping("/my-ott-submit")
|
||||
fun ottSubmitPage(): String {
|
||||
return "my-ott-submit"
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.disablingdefaultsubmitpage
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
open class SecurityConfig {
|
||||
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(pathMatchers("/my-ott-submit"), permitAll)
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
formLogin { }
|
||||
oneTimeTokenLogin {
|
||||
showDefaultSubmitPage = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.sendingtokentouser
|
||||
|
||||
import org.springframework.mail.MailSender
|
||||
import org.springframework.mail.SimpleMailMessage
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.security.web.server.authentication.ott.ServerRedirectOneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.util.UriComponentsBuilder
|
||||
import reactor.core.publisher.Mono
|
||||
import java.util.function.Function
|
||||
|
||||
|
||||
// tag::snippet[]
|
||||
@Component // <1>
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender) : ServerOneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
private val redirectHandler: ServerOneTimeTokenGenerationSuccessHandler = ServerRedirectOneTimeTokenGenerationSuccessHandler("/ott/sent")
|
||||
|
||||
override fun handle(exchange: ServerWebExchange, oneTimeToken: OneTimeToken): Mono<Void> {
|
||||
|
||||
return Mono.just(exchange.request)
|
||||
.map(Function { request ->
|
||||
UriComponentsBuilder.fromUri(request.uri)
|
||||
.replacePath(request.path.contextPath().value())
|
||||
.replaceQuery(null)
|
||||
.fragment(null)
|
||||
.path("/login/ott")
|
||||
.queryParam("token", oneTimeToken.getTokenValue())
|
||||
.toUriString() // <2>
|
||||
})
|
||||
.flatMap(Function { uri ->
|
||||
val email = getUserEmail(oneTimeToken.getUsername()) // <3>
|
||||
val message = SimpleMailMessage()
|
||||
message.setTo(email)
|
||||
message.subject = "Your Spring Security One Time Token"
|
||||
message.text = "Use the following link to sign in into the application: $uri"
|
||||
this.mailSender.send(message) // <4>
|
||||
Mono.empty()
|
||||
})
|
||||
.then(this.redirectHandler.handle(exchange, oneTimeToken)) // <5>
|
||||
}
|
||||
|
||||
private fun getUserEmail(username: String): String {
|
||||
/**/ return username
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Controller
|
||||
class PageController {
|
||||
|
||||
@GetMapping("/ott/sent")
|
||||
fun ottSent(): String {
|
||||
return "my-template"
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.reactive.authentication.sendingtokentouser
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
open class SecurityConfig {
|
||||
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oneTimeTokenLogin { }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.changinggenerateurl
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
override fun handle(request: HttpServletRequest, response: HttpServletResponse, oneTimeToken: OneTimeToken) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.changinggenerateurl
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
//...
|
||||
formLogin { }
|
||||
oneTimeTokenLogin {
|
||||
tokenGeneratingUrl = "/ott/my-generate-url"
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
// end::config[]
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.changingsubmitpageurl
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
override fun handle(request: HttpServletRequest, response: HttpServletResponse, oneTimeToken: OneTimeToken) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.changingsubmitpageurl
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
//...
|
||||
formLogin { }
|
||||
oneTimeTokenLogin {
|
||||
defaultSubmitPageUrl = "/ott/submit"
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
// end::config[]
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.customizegenerateconsumetoken
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
override fun handle(request: HttpServletRequest, response: HttpServletResponse, oneTimeToken: OneTimeToken) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.customizegenerateconsumetoken
|
||||
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenService
|
||||
|
||||
class MyCustomOneTimeTokenService: OneTimeTokenService {
|
||||
|
||||
override fun generate(request: GenerateOneTimeTokenRequest): OneTimeToken {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun consume(authenticationToken: OneTimeTokenAuthenticationToken): OneTimeToken? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.customizegenerateconsumetoken
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenService
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class OneTimeTokenServiceBeanSecurityConfig {
|
||||
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
//...
|
||||
formLogin { }
|
||||
oneTimeTokenLogin { }
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun oneTimeTokenService(): OneTimeTokenService {
|
||||
return MyCustomOneTimeTokenService()
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.customizegenerateconsumetoken
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class OneTimeTokenServiceDSLSecurityConfig {
|
||||
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
//...
|
||||
formLogin { }
|
||||
oneTimeTokenLogin {
|
||||
tokenService = MyCustomOneTimeTokenService()
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.customizegeneratetokenrequest
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver
|
||||
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver
|
||||
import java.time.Duration
|
||||
|
||||
open class SecurityConfig {
|
||||
|
||||
// tag::config[]
|
||||
@Bean
|
||||
open fun generateRequestResolver() : GenerateOneTimeTokenRequestResolver {
|
||||
return DefaultGenerateOneTimeTokenRequestResolver().apply {
|
||||
this.setExpiresIn(Duration.ofMinutes(10))
|
||||
}
|
||||
}
|
||||
// end::config[]
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.disablingdefaultsubmitpage
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
// tag::snippet[]
|
||||
@Component
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
override fun handle(request: HttpServletRequest, response: HttpServletResponse, oneTimeToken: OneTimeToken) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.disablingdefaultsubmitpage
|
||||
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
|
||||
// tag::snippet[]
|
||||
@Controller
|
||||
class MyController {
|
||||
|
||||
@GetMapping("/my-ott-submit")
|
||||
fun ottSubmitPage(): String {
|
||||
return "my-ott-submit"
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.disablingdefaultsubmitpage
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open 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()
|
||||
}
|
||||
|
||||
}
|
||||
// end::config[]
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.sendingtokentouser
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.mail.MailSender
|
||||
import org.springframework.mail.SimpleMailMessage
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
|
||||
import org.springframework.security.web.util.UrlUtils
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.util.UriComponentsBuilder
|
||||
|
||||
// tag::snippet[]
|
||||
@Component // <1>
|
||||
class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender) : OneTimeTokenGenerationSuccessHandler {
|
||||
|
||||
private val redirectHandler: OneTimeTokenGenerationSuccessHandler = RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent")
|
||||
|
||||
override fun handle(request: HttpServletRequest, response: HttpServletResponse, oneTimeToken: OneTimeToken) {
|
||||
val builder = UriComponentsBuilder.fromUriString(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>
|
||||
val message = SimpleMailMessage()
|
||||
message.setTo(email)
|
||||
message.subject = "Your Spring Security One Time Token"
|
||||
message.text = "Use the following link to sign in into the application: $magicLink"
|
||||
this.mailSender.send(message) // <4>
|
||||
this.redirectHandler.handle(request, response, oneTimeToken) // <5>
|
||||
}
|
||||
|
||||
private fun getUserEmail(username: String): String {
|
||||
/**/ return username
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
class PageController {
|
||||
|
||||
@GetMapping("/ott/sent")
|
||||
fun ottSent(): String {
|
||||
return "my-template"
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.authentication.sendingtokentouser
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
formLogin { }
|
||||
oneTimeTokenLogin { }
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
// end::config[]
|
||||
@@ -105,7 +105,7 @@ org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-inf
|
||||
org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8.0.1969"
|
||||
org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1"
|
||||
|
||||
spring-nullability = 'io.spring.nullability:io.spring.nullability.gradle.plugin:0.0.14'
|
||||
spring-nullability = 'io.spring.nullability:io.spring.nullability.gradle.plugin:0.0.15'
|
||||
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.31.9.RELEASE'
|
||||
com-password4j-password4j = { module = "com.password4j:password4j", version.ref = "com-password4j" }
|
||||
|
||||
|
||||
+29
-3
@@ -24,9 +24,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
@@ -79,7 +82,7 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
|
||||
.map((c) -> c.get(ServerWebExchange.class));
|
||||
// @formatter:on
|
||||
|
||||
private ServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
private ServerSecurityContextRepository serverSecurityContextRepository = new NonRotatingWebSessionServerSecurityContextRepository();
|
||||
|
||||
private ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
|
||||
|
||||
@@ -141,8 +144,7 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
|
||||
|
||||
/**
|
||||
* Sets a {@link ServerSecurityContextRepository} to use for refreshing a
|
||||
* {@link SecurityContext}, defaults to
|
||||
* {@link WebSessionServerSecurityContextRepository}.
|
||||
* {@link SecurityContext}.
|
||||
* @param serverSecurityContextRepository the {@link ServerSecurityContextRepository}
|
||||
* to use
|
||||
*/
|
||||
@@ -316,4 +318,28 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
|
||||
return this.serverSecurityContextRepository.save(exchange, securityContext);
|
||||
}
|
||||
|
||||
private static final class NonRotatingWebSessionServerSecurityContextRepository
|
||||
implements ServerSecurityContextRepository {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(NonRotatingWebSessionServerSecurityContextRepository.class);
|
||||
|
||||
@Override
|
||||
public Mono<SecurityContext> load(ServerWebExchange exchange) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save(ServerWebExchange exchange, @Nullable SecurityContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
// Save SecurityContext in WebSession without rotating session id.
|
||||
return exchange.getSession().doOnNext((session) -> {
|
||||
session.getAttributes()
|
||||
.put(WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME, context);
|
||||
logger.debug(LogMessage.format("Saved SecurityContext '%s' in WebSession: '%s'", context, session));
|
||||
}).then();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ public final class InMemoryOidcSessionRegistry implements OidcSessionRegistry {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OidcSessionInformation removeSessionInformation(String clientSessionId) {
|
||||
public @Nullable OidcSessionInformation removeSessionInformation(String clientSessionId) {
|
||||
OidcSessionInformation information = this.sessions.remove(clientSessionId);
|
||||
if (information != null) {
|
||||
this.logger.trace("Removed client session");
|
||||
|
||||
+3
-1
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.oauth2.client.oidc.session;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
|
||||
/**
|
||||
@@ -44,7 +46,7 @@ public interface OidcSessionRegistry {
|
||||
* @param clientSessionId the client session
|
||||
* @return any found {@link OidcSessionInformation}, could be {@code null}
|
||||
*/
|
||||
OidcSessionInformation removeSessionInformation(String clientSessionId);
|
||||
@Nullable OidcSessionInformation removeSessionInformation(String clientSessionId);
|
||||
|
||||
/**
|
||||
* Deregister the OIDC Provider sessions referenced by the provided OIDC Logout Token
|
||||
|
||||
+47
@@ -50,7 +50,9 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
@@ -140,6 +142,51 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
// gh-19424
|
||||
@Test
|
||||
void onAuthorizationSuccessWhenDefaultServerSecurityContextRepositoryThenWebSessionIdNotChanged() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
Instant authTime = Instant.now();
|
||||
DefaultOidcUser principal = createOidcUser(authTime);
|
||||
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
|
||||
principal.getAuthorities(), clientRegistration.getRegistrationId());
|
||||
OAuth2AccessToken accessToken = createAccessToken();
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
|
||||
accessToken, null);
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
|
||||
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
|
||||
OidcParameterNames.ID_TOKEN, "id-token-1234");
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("iss", principal.getIssuer());
|
||||
claims.put("sub", principal.getSubject());
|
||||
claims.put("aud", principal.getAudience());
|
||||
claims.put("auth_time", authTime);
|
||||
claims.put("nonce", principal.getNonce());
|
||||
Jwt jwt = mock(Jwt.class);
|
||||
given(jwt.getTokenValue()).willReturn("id-token-1234");
|
||||
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt());
|
||||
given(jwt.getClaims()).willReturn(claims);
|
||||
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
|
||||
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
|
||||
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
|
||||
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
|
||||
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
|
||||
given(userService.loadUser(any())).willReturn(Mono.just(principal));
|
||||
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
|
||||
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
|
||||
handler.setUserService(userService);
|
||||
String originalSessionId = exchange.getSession().map(WebSession::getId).block();
|
||||
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
|
||||
.verifyComplete();
|
||||
StepVerifier.create(exchange.getSession())
|
||||
.assertNext((session) -> assertThat(session.getId()).isEqualTo(originalSessionId))
|
||||
.verifyComplete();
|
||||
WebSessionServerSecurityContextRepository securityContextRepository = new WebSessionServerSecurityContextRepository();
|
||||
StepVerifier.create(securityContextRepository.load(exchange).mapNotNull(SecurityContext::getAuthentication))
|
||||
.expectNext(authenticationToken)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void onAuthorizationSuccessWhenIdTokenIssuerNotSameThenException() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
|
||||
+8
@@ -35,6 +35,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -77,6 +78,7 @@ import org.springframework.web.reactive.function.client.ExchangeFunction;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -320,6 +322,8 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionITests {
|
||||
doReturn(Mono.just(authorizedClient)).when(this.authorizedClientRepository)
|
||||
.loadAuthorizedClient(eq(clientRegistration.getRegistrationId()), eq(this.authentication),
|
||||
eq(this.exchange));
|
||||
// Capture the original session id.
|
||||
String originalSessionId = this.exchange.getSession().map(WebSession::getId).block();
|
||||
this.webClient.get()
|
||||
.uri(this.serverUrl)
|
||||
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction
|
||||
@@ -356,6 +360,10 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionITests {
|
||||
assertThat(oidcUser.getSubject()).isEqualTo("subject-1234");
|
||||
assertThat(oidcUser.getName()).isEqualTo("refreshed-username");
|
||||
});
|
||||
// Verify that session id was not changed.
|
||||
StepVerifier.create(this.exchange.getSession())
|
||||
.assertNext((session) -> assertThat(session.getId()).isEqualTo(originalSessionId))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-2
@@ -53,7 +53,7 @@ public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthentication
|
||||
public BearerTokenAuthentication(OAuth2AuthenticatedPrincipal principal, OAuth2AccessToken credentials,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
super(credentials, principal, credentials, authorities);
|
||||
Assert.isTrue(credentials.getTokenType() == OAuth2AccessToken.TokenType.BEARER,
|
||||
Assert.isTrue(OAuth2AccessToken.TokenType.BEARER.equals(credentials.getTokenType()),
|
||||
"credentials must be a bearer token");
|
||||
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(principal.getAttributes()));
|
||||
setAuthenticated(true);
|
||||
@@ -121,7 +121,8 @@ public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthentication
|
||||
*/
|
||||
@Override
|
||||
public B token(OAuth2AccessToken token) {
|
||||
Assert.isTrue(token.getTokenType() == OAuth2AccessToken.TokenType.BEARER, "token must be a bearer token");
|
||||
Assert.isTrue(OAuth2AccessToken.TokenType.BEARER.equals(token.getTokenType()),
|
||||
"token must be a bearer token");
|
||||
super.credentials(token);
|
||||
return super.token(token);
|
||||
}
|
||||
|
||||
+28
@@ -173,4 +173,32 @@ public class BearerTokenAuthenticationTests {
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
// gh-19377
|
||||
@Test
|
||||
public void compareCredentialsHasBearerTokenType() {
|
||||
Instant current = Instant.now();
|
||||
Instant after1hour = Instant.now().plusSeconds(3600);
|
||||
OAuth2AccessToken oAuth2AccessToken = new OAuth2AccessToken(new OAuth2AccessToken.TokenType("Bearer"), "token",
|
||||
current, after1hour);
|
||||
BearerTokenAuthentication authenticated = new BearerTokenAuthentication(this.principal, oAuth2AccessToken,
|
||||
this.authorities);
|
||||
assertThat(authenticated.getName()).isEqualTo(this.name);
|
||||
assertThat(authenticated.getCredentials())
|
||||
.isEqualTo(new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", current, after1hour));
|
||||
}
|
||||
|
||||
// gh-19377
|
||||
@Test
|
||||
public void toBuilderCompareCredentialsHasBearerTokenType() {
|
||||
Instant current = Instant.now();
|
||||
Instant after1hour = Instant.now().plusSeconds(3600);
|
||||
OAuth2AccessToken oAuth2AccessToken = new OAuth2AccessToken(new OAuth2AccessToken.TokenType("Bearer"), "token",
|
||||
current, after1hour);
|
||||
BearerTokenAuthentication token = new BearerTokenAuthentication(this.principal, this.token, this.authorities);
|
||||
BearerTokenAuthentication authenticated = token.toBuilder().token(oAuth2AccessToken).build();
|
||||
assertThat(authenticated.getName()).isEqualTo(this.name);
|
||||
assertThat(authenticated.getCredentials())
|
||||
.isEqualTo(new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", current, after1hour));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.security.web.header;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
@@ -114,6 +115,8 @@ public class HeaderWriterFilter extends OncePerRequestFilter {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
private final AtomicBoolean headersWritten = new AtomicBoolean(false);
|
||||
|
||||
HeaderWriterResponse(HttpServletRequest request, HttpServletResponse response) {
|
||||
super(response);
|
||||
this.request = request;
|
||||
@@ -129,7 +132,9 @@ public class HeaderWriterFilter extends OncePerRequestFilter {
|
||||
if (isDisableOnResponseCommitted()) {
|
||||
return;
|
||||
}
|
||||
HeaderWriterFilter.this.writeHeaders(this.request, getHttpResponse());
|
||||
if (this.headersWritten.compareAndSet(false, true)) {
|
||||
HeaderWriterFilter.this.writeHeaders(this.request, getHttpResponse());
|
||||
}
|
||||
}
|
||||
|
||||
private HttpServletResponse getHttpResponse() {
|
||||
|
||||
+19
@@ -113,6 +113,25 @@ public class HeaderWriterFilterTests {
|
||||
verifyNoMoreInteractions(this.writer1);
|
||||
}
|
||||
|
||||
// gh-9175
|
||||
@Test
|
||||
public void doFilterWhenWriteHeadersCalledConcurrentlyThenHeadersWrittenOnlyOnce() throws Exception {
|
||||
List<HeaderWriter> headerWriters = new ArrayList<>();
|
||||
headerWriters.add(this.writer1);
|
||||
HeaderWriterFilter filter = new HeaderWriterFilter(headerWriters);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(request, response, (req, resp) ->
|
||||
// Calling writeHeaders() directly simulates the race window where an
|
||||
// async thread enters writeHeaders() via onResponseCommitted() but has
|
||||
// not yet called disableOnResponseCommitted().
|
||||
((HeaderWriterFilter.HeaderWriterResponse) resp).writeHeaders());
|
||||
// The finally block in doHeadersAfter also calls writeHeaders().
|
||||
// Without the fix, the header writers would be invoked twice.
|
||||
verify(this.writer1).writeHeaders(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
verifyNoMoreInteractions(this.writer1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWrittenAtBeginningOfRequest() throws Exception {
|
||||
HeaderWriterFilter filter = new HeaderWriterFilter(Collections.singletonList(this.writer1));
|
||||
|
||||
Reference in New Issue
Block a user