mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-26 22:02:41 +00:00
Add Support ServerGenerateOneTimeTokenRequestResolver
Closes gh-16488 Signed-off-by: Max Batischev <mblancer@mail.ru>
This commit is contained in:
parent
981e3fd779
commit
be81377235
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -29,6 +29,7 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@ -53,6 +54,7 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
|
||||
import org.springframework.security.authentication.ott.OneTimeToken;
|
||||
import org.springframework.security.authentication.ott.reactive.InMemoryReactiveOneTimeTokenService;
|
||||
import org.springframework.security.authentication.ott.reactive.OneTimeTokenReactiveAuthenticationManager;
|
||||
@ -156,7 +158,9 @@ import org.springframework.security.web.server.authentication.logout.LogoutWebFi
|
||||
import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler;
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
|
||||
import org.springframework.security.web.server.authentication.ott.DefaultServerGenerateOneTimeTokenRequestResolver;
|
||||
import org.springframework.security.web.server.authentication.ott.GenerateOneTimeTokenWebFilter;
|
||||
import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver;
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenAuthenticationConverter;
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler;
|
||||
import org.springframework.security.web.server.authorization.AuthorizationContext;
|
||||
@ -5940,6 +5944,8 @@ public class ServerHttpSecurity {
|
||||
|
||||
private ServerSecurityContextRepository securityContextRepository;
|
||||
|
||||
private ServerGenerateOneTimeTokenRequestResolver requestResolver;
|
||||
|
||||
private String loginProcessingUrl = "/login/ott";
|
||||
|
||||
private String defaultSubmitPageUrl = "/login/ott";
|
||||
@ -5985,6 +5991,7 @@ public class ServerHttpSecurity {
|
||||
getTokenGenerationSuccessHandler());
|
||||
generateFilter
|
||||
.setRequestMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, this.tokenGeneratingUrl));
|
||||
generateFilter.setGenerateRequestResolver(getRequestResolver());
|
||||
http.addFilterAt(generateFilter, SecurityWebFiltersOrder.ONE_TIME_TOKEN);
|
||||
}
|
||||
|
||||
@ -6112,6 +6119,32 @@ public class ServerHttpSecurity {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link ServerGenerateOneTimeTokenRequestResolver} when resolving
|
||||
* {@link GenerateOneTimeTokenRequest} from {@link ServerWebExchange}. By default,
|
||||
* the {@link DefaultServerGenerateOneTimeTokenRequestResolver} is used.
|
||||
* @param requestResolver the
|
||||
* {@link DefaultServerGenerateOneTimeTokenRequestResolver} to use
|
||||
* @since 6.5
|
||||
*/
|
||||
public OneTimeTokenLoginSpec generateRequestResolver(
|
||||
ServerGenerateOneTimeTokenRequestResolver requestResolver) {
|
||||
Assert.notNull(requestResolver, "generateRequestResolver cannot be null");
|
||||
this.requestResolver = requestResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
private ServerGenerateOneTimeTokenRequestResolver getRequestResolver() {
|
||||
if (this.requestResolver != null) {
|
||||
return this.requestResolver;
|
||||
}
|
||||
ServerGenerateOneTimeTokenRequestResolver bean = getBeanOrNull(
|
||||
ServerGenerateOneTimeTokenRequestResolver.class);
|
||||
this.requestResolver = Objects.requireNonNullElseGet(bean,
|
||||
DefaultServerGenerateOneTimeTokenRequestResolver::new);
|
||||
return this.requestResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the URL to process the login request, defaults to {@code /login/ott}.
|
||||
* Only POST requests are processed, for that reason make sure that you pass a
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -18,6 +18,7 @@ package org.springframework.security.config.web.server
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.authentication.ott.reactive.ReactiveOneTimeTokenService
|
||||
import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
|
||||
@ -34,6 +35,7 @@ import org.springframework.security.web.server.context.ServerSecurityContextRepo
|
||||
* @property authenticationConverter Use this [ServerAuthenticationConverter] when converting incoming requests to an authentication
|
||||
* @property authenticationFailureHandler the [ServerAuthenticationFailureHandler] to use when authentication
|
||||
* @property authenticationSuccessHandler the [ServerAuthenticationSuccessHandler] to be used
|
||||
* @property generateRequestResolver the [ServerGenerateOneTimeTokenRequestResolver] to be used
|
||||
* @property defaultSubmitPageUrl sets the URL that the default submit page will be generated
|
||||
* @property showDefaultSubmitPage configures whether the default one-time token submit page should be shown
|
||||
* @property loginProcessingUrl the URL to process the login request
|
||||
@ -50,6 +52,7 @@ class ServerOneTimeTokenLoginDsl {
|
||||
var authenticationSuccessHandler: ServerAuthenticationSuccessHandler? = null
|
||||
var tokenGenerationSuccessHandler: ServerOneTimeTokenGenerationSuccessHandler? = null
|
||||
var securityContextRepository: ServerSecurityContextRepository? = null
|
||||
var generateRequestResolver: ServerGenerateOneTimeTokenRequestResolver? = null
|
||||
var defaultSubmitPageUrl: String? = null
|
||||
var loginProcessingUrl: String? = null
|
||||
var tokenGeneratingUrl: String? = null
|
||||
@ -71,6 +74,7 @@ class ServerOneTimeTokenLoginDsl {
|
||||
)
|
||||
}
|
||||
securityContextRepository?.also { oneTimeTokenLogin.securityContextRepository(securityContextRepository) }
|
||||
generateRequestResolver?.also { oneTimeTokenLogin.generateRequestResolver(generateRequestResolver) }
|
||||
defaultSubmitPageUrl?.also { oneTimeTokenLogin.defaultSubmitPageUrl(defaultSubmitPageUrl) }
|
||||
showDefaultSubmitPage?.also { oneTimeTokenLogin.showDefaultSubmitPage(showDefaultSubmitPage!!) }
|
||||
loginProcessingUrl?.also { oneTimeTokenLogin.loginProcessingUrl(loginProcessingUrl) }
|
||||
|
@ -271,10 +271,10 @@ public class OneTimeTokenLoginSpecTests {
|
||||
@Test
|
||||
void oneTimeTokenWhenNoOneTimeTokenGenerationSuccessHandlerThenException() {
|
||||
assertThatException()
|
||||
.isThrownBy(() -> this.spring.register(OneTimeTokenNotGeneratedOttHandlerConfig.class).autowire())
|
||||
.havingRootCause()
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.withMessage("""
|
||||
.isThrownBy(() -> this.spring.register(OneTimeTokenNotGeneratedOttHandlerConfig.class).autowire())
|
||||
.havingRootCause()
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.withMessage("""
|
||||
A ServerOneTimeTokenGenerationSuccessHandler is required to enable oneTimeTokenLogin().
|
||||
Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL.
|
||||
""");
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.config.web.server
|
||||
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import reactor.core.publisher.Mono
|
||||
@ -26,6 +27,7 @@ import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.Import
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest
|
||||
import org.springframework.security.authentication.ott.OneTimeToken
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
@ -34,6 +36,8 @@ import org.springframework.security.core.userdetails.MapReactiveUserDetailsServi
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers
|
||||
import org.springframework.security.web.server.authentication.ott.DefaultServerGenerateOneTimeTokenRequestResolver
|
||||
import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler
|
||||
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler
|
||||
@ -43,6 +47,9 @@ import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import org.springframework.web.reactive.function.BodyInserters
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.util.UriBuilder
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* Tests for [ServerOneTimeTokenLoginDsl]
|
||||
@ -146,6 +153,48 @@ class ServerOneTimeTokenLoginDslTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `oneTimeToken when custom token expiration time set then authenticate`() {
|
||||
spring.register(OneTimeTokenConfigWithCustomTokenExpirationTime::class.java).autowire()
|
||||
|
||||
// @formatter:off
|
||||
client.mutateWith(SecurityMockServerConfigurers.csrf())
|
||||
.post()
|
||||
.uri{ uriBuilder: UriBuilder -> uriBuilder
|
||||
.path("/ott/generate")
|
||||
.build()
|
||||
}
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body(BodyInserters.fromFormData("username", "user"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.is3xxRedirection()
|
||||
.expectHeader().valueEquals("Location", "/login/ott")
|
||||
|
||||
client.mutateWith(SecurityMockServerConfigurers.csrf())
|
||||
.post()
|
||||
.uri{ uriBuilder:UriBuilder -> uriBuilder
|
||||
.path("/ott/generate")
|
||||
.build()
|
||||
}
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body(BodyInserters.fromFormData("username", "user"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.is3xxRedirection()
|
||||
.expectHeader().valueEquals("Location", "/login/ott")
|
||||
|
||||
val token = TestServerOneTimeTokenGenerationSuccessHandler.lastToken
|
||||
|
||||
Assertions.assertThat(getCurrentMinutes(token!!.expiresAt)).isEqualTo(10)
|
||||
}
|
||||
|
||||
private fun getCurrentMinutes(expiresAt:Instant): Int {
|
||||
val expiresMinutes = expiresAt.atZone(ZoneOffset.UTC).minute
|
||||
val currentMinutes = Instant.now().atZone(ZoneOffset.UTC).minute
|
||||
return expiresMinutes - currentMinutes
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
@ -199,6 +248,34 @@ class ServerOneTimeTokenLoginDslTests {
|
||||
MapReactiveUserDetailsService(User("user", "password", listOf()))
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
@Import(OneTimeTokenLoginSpecTests.UserDetailsServiceConfig::class)
|
||||
open class OneTimeTokenConfigWithCustomTokenExpirationTime {
|
||||
@Bean
|
||||
open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
// @formatter:off
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oneTimeTokenLogin {
|
||||
tokenGenerationSuccessHandler = TestServerOneTimeTokenGenerationSuccessHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun resolver(): ServerGenerateOneTimeTokenRequestResolver {
|
||||
val resolver = DefaultServerGenerateOneTimeTokenRequestResolver()
|
||||
return ServerGenerateOneTimeTokenRequestResolver { exchange ->
|
||||
resolver.resolve(exchange)
|
||||
.map { request -> GenerateOneTimeTokenRequest(request.username, Duration.ofSeconds(600)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestServerOneTimeTokenGenerationSuccessHandler: ServerOneTimeTokenGenerationSuccessHandler {
|
||||
private var delegate: ServerRedirectOneTimeTokenGenerationSuccessHandler? = null
|
||||
|
||||
|
@ -546,3 +546,35 @@ class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender):
|
||||
|
||||
----
|
||||
======
|
||||
|
||||
[[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))
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.web.server.authentication.ott;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ServerGenerateOneTimeTokenRequestResolver}. Resolves
|
||||
* {@link GenerateOneTimeTokenRequest} from username parameter.
|
||||
*
|
||||
* @author Max Batischev
|
||||
* @since 6.5
|
||||
*/
|
||||
public final class DefaultServerGenerateOneTimeTokenRequestResolver
|
||||
implements ServerGenerateOneTimeTokenRequestResolver {
|
||||
|
||||
private static final String USERNAME = "username";
|
||||
|
||||
private static final Duration DEFAULT_EXPIRES_IN = Duration.ofMinutes(5);
|
||||
|
||||
private Duration expiresIn = DEFAULT_EXPIRES_IN;
|
||||
|
||||
@Override
|
||||
public Mono<GenerateOneTimeTokenRequest> resolve(ServerWebExchange exchange) {
|
||||
// @formatter:off
|
||||
return exchange.getFormData()
|
||||
.mapNotNull((data) -> data.getFirst(USERNAME))
|
||||
.switchIfEmpty(Mono.empty())
|
||||
.map((username) -> new GenerateOneTimeTokenRequest(username, this.expiresIn));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one-time token expiration time
|
||||
* @param expiresIn one-time token expiration time
|
||||
*/
|
||||
public void setExpiresIn(Duration expiresIn) {
|
||||
Assert.notNull(expiresIn, "expiresIn cannot be null");
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -19,7 +19,6 @@ package org.springframework.security.web.server.authentication.ott;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
|
||||
import org.springframework.security.authentication.ott.reactive.ReactiveOneTimeTokenService;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
|
||||
@ -37,12 +36,12 @@ import org.springframework.web.server.WebFilterChain;
|
||||
*/
|
||||
public final class GenerateOneTimeTokenWebFilter implements WebFilter {
|
||||
|
||||
private static final String USERNAME = "username";
|
||||
|
||||
private final ReactiveOneTimeTokenService oneTimeTokenService;
|
||||
|
||||
private ServerWebExchangeMatcher matcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/ott/generate");
|
||||
|
||||
private ServerGenerateOneTimeTokenRequestResolver generateRequestResolver = new DefaultServerGenerateOneTimeTokenRequestResolver();
|
||||
|
||||
private final ServerOneTimeTokenGenerationSuccessHandler oneTimeTokenGenerationSuccessHandler;
|
||||
|
||||
public GenerateOneTimeTokenWebFilter(ReactiveOneTimeTokenService oneTimeTokenService,
|
||||
@ -58,10 +57,9 @@ public final class GenerateOneTimeTokenWebFilter implements WebFilter {
|
||||
// @formatter:off
|
||||
return this.matcher.matches(exchange)
|
||||
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
|
||||
.then(exchange.getFormData())
|
||||
.mapNotNull((data) -> data.getFirst(USERNAME))
|
||||
.flatMap((result) -> this.generateRequestResolver.resolve(exchange))
|
||||
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
|
||||
.flatMap((username) -> this.oneTimeTokenService.generate(new GenerateOneTimeTokenRequest(username)))
|
||||
.flatMap(this.oneTimeTokenService::generate)
|
||||
.flatMap((token) -> this.oneTimeTokenGenerationSuccessHandler.handle(exchange, token));
|
||||
// @formatter:on
|
||||
}
|
||||
@ -75,4 +73,15 @@ public final class GenerateOneTimeTokenWebFilter implements WebFilter {
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given {@link ServerGenerateOneTimeTokenRequestResolver} to resolve the
|
||||
* request, defaults to {@link DefaultServerGenerateOneTimeTokenRequestResolver}
|
||||
* @param requestResolver {@link ServerGenerateOneTimeTokenRequestResolver}
|
||||
* @since 6.5
|
||||
*/
|
||||
public void setGenerateRequestResolver(ServerGenerateOneTimeTokenRequestResolver requestResolver) {
|
||||
Assert.notNull(requestResolver, "requestResolver cannot be null");
|
||||
this.generateRequestResolver = requestResolver;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.web.server.authentication.ott;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* A strategy for resolving a {@link GenerateOneTimeTokenRequest} from the
|
||||
* {@link ServerWebExchange}.
|
||||
*
|
||||
* @author Max Batischev
|
||||
* @since 6.5
|
||||
*/
|
||||
public interface ServerGenerateOneTimeTokenRequestResolver {
|
||||
|
||||
/**
|
||||
* Resolves {@link GenerateOneTimeTokenRequest} from {@link ServerWebExchange}
|
||||
* @param exchange {@link ServerWebExchange} to resolve
|
||||
* @return {@link GenerateOneTimeTokenRequest}
|
||||
*/
|
||||
Mono<GenerateOneTimeTokenRequest> resolve(ServerWebExchange exchange);
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.web.server.authentication.ott;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultServerGenerateOneTimeTokenRequestResolver}
|
||||
*
|
||||
* @author Max Batischev
|
||||
*/
|
||||
public class DefaultServerGenerateOneTimeTokenRequestResolverTests {
|
||||
|
||||
private final DefaultServerGenerateOneTimeTokenRequestResolver resolver = new DefaultServerGenerateOneTimeTokenRequestResolver();
|
||||
|
||||
@Test
|
||||
void resolveWhenUsernameParameterIsPresentThenResolvesGenerateRequest() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/ott/generate")
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body("username=user"));
|
||||
|
||||
GenerateOneTimeTokenRequest request = this.resolver.resolve(exchange).block();
|
||||
|
||||
assertThat(request).isNotNull();
|
||||
assertThat(request.getUsername()).isEqualTo("user");
|
||||
assertThat(request.getExpiresIn()).isEqualTo(Duration.ofMinutes(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenUsernameParameterIsNotPresentThenNull() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.post("/ott/generate").contentType(MediaType.APPLICATION_FORM_URLENCODED));
|
||||
|
||||
GenerateOneTimeTokenRequest request = this.resolver.resolve(exchange).block();
|
||||
|
||||
assertThat(request).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenExpiresInSetThenResolvesGenerateRequest() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/ott/generate")
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body("username=user"));
|
||||
this.resolver.setExpiresIn(Duration.ofSeconds(600));
|
||||
|
||||
GenerateOneTimeTokenRequest generateRequest = this.resolver.resolve(exchange).block();
|
||||
|
||||
assertThat(generateRequest.getExpiresIn()).isEqualTo(Duration.ofSeconds(600));
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user