1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Add Support ServerGenerateOneTimeTokenRequestResolver

Closes gh-16488

Signed-off-by: Max Batischev <mblancer@mail.ru>
This commit is contained in:
Max Batischev
2025-01-27 18:18:30 +03:00
committed by Josh Cummings
parent 981e3fd779
commit be81377235
9 changed files with 345 additions and 14 deletions
@@ -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));
}
}