1
1
/*
2
- * Copyright 2002-2024 the original author or authors.
2
+ * Copyright 2002-2025 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
16
16
17
17
package org.springframework.security.config.web.server
18
18
19
+ import org.assertj.core.api.Assertions
19
20
import org.junit.jupiter.api.Test
20
21
import org.junit.jupiter.api.extension.ExtendWith
21
22
import reactor.core.publisher.Mono
@@ -26,6 +27,7 @@ import org.springframework.context.annotation.Configuration
26
27
import org.springframework.context.annotation.Import
27
28
import org.springframework.context.ApplicationContext
28
29
import org.springframework.http.MediaType
30
+ import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest
29
31
import org.springframework.security.authentication.ott.OneTimeToken
30
32
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
31
33
import org.springframework.security.config.test.SpringTestContext
@@ -34,6 +36,8 @@ import org.springframework.security.core.userdetails.MapReactiveUserDetailsServi
34
36
import org.springframework.security.core.userdetails.ReactiveUserDetailsService
35
37
import org.springframework.security.core.userdetails.User
36
38
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers
39
+ import org.springframework.security.web.server.authentication.ott.DefaultServerGenerateOneTimeTokenRequestResolver
40
+ import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver
37
41
import org.springframework.security.web.server.SecurityWebFilterChain
38
42
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler
39
43
import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler
@@ -43,6 +47,9 @@ import org.springframework.web.reactive.config.EnableWebFlux
43
47
import org.springframework.web.reactive.function.BodyInserters
44
48
import org.springframework.web.server.ServerWebExchange
45
49
import org.springframework.web.util.UriBuilder
50
+ import java.time.Duration
51
+ import java.time.Instant
52
+ import java.time.ZoneOffset
46
53
47
54
/* *
48
55
* Tests for [ServerOneTimeTokenLoginDsl]
@@ -146,6 +153,48 @@ class ServerOneTimeTokenLoginDslTests {
146
153
// @formatter:on
147
154
}
148
155
156
+ @Test
157
+ fun `oneTimeToken when custom token expiration time set then authenticate` () {
158
+ spring.register(OneTimeTokenConfigWithCustomTokenExpirationTime ::class .java).autowire()
159
+
160
+ // @formatter:off
161
+ client.mutateWith(SecurityMockServerConfigurers .csrf())
162
+ .post()
163
+ .uri{ uriBuilder: UriBuilder -> uriBuilder
164
+ .path(" /ott/generate" )
165
+ .build()
166
+ }
167
+ .contentType(MediaType .APPLICATION_FORM_URLENCODED )
168
+ .body(BodyInserters .fromFormData(" username" , " user" ))
169
+ .exchange()
170
+ .expectStatus()
171
+ .is3xxRedirection()
172
+ .expectHeader().valueEquals(" Location" , " /login/ott" )
173
+
174
+ client.mutateWith(SecurityMockServerConfigurers .csrf())
175
+ .post()
176
+ .uri{ uriBuilder: UriBuilder -> uriBuilder
177
+ .path(" /ott/generate" )
178
+ .build()
179
+ }
180
+ .contentType(MediaType .APPLICATION_FORM_URLENCODED )
181
+ .body(BodyInserters .fromFormData(" username" , " user" ))
182
+ .exchange()
183
+ .expectStatus()
184
+ .is3xxRedirection()
185
+ .expectHeader().valueEquals(" Location" , " /login/ott" )
186
+
187
+ val token = TestServerOneTimeTokenGenerationSuccessHandler .lastToken
188
+
189
+ Assertions .assertThat(getCurrentMinutes(token!! .expiresAt)).isEqualTo(10 )
190
+ }
191
+
192
+ private fun getCurrentMinutes (expiresAt : Instant ): Int {
193
+ val expiresMinutes = expiresAt.atZone(ZoneOffset .UTC ).minute
194
+ val currentMinutes = Instant .now().atZone(ZoneOffset .UTC ).minute
195
+ return expiresMinutes - currentMinutes
196
+ }
197
+
149
198
@Configuration
150
199
@EnableWebFlux
151
200
@EnableWebFluxSecurity
@@ -199,6 +248,34 @@ class ServerOneTimeTokenLoginDslTests {
199
248
MapReactiveUserDetailsService (User (" user" , " password" , listOf ()))
200
249
}
201
250
251
+ @Configuration(proxyBeanMethods = false )
252
+ @EnableWebFlux
253
+ @EnableWebFluxSecurity
254
+ @Import(OneTimeTokenLoginSpecTests .UserDetailsServiceConfig ::class )
255
+ open class OneTimeTokenConfigWithCustomTokenExpirationTime {
256
+ @Bean
257
+ open fun securityWebFilterChain (http : ServerHttpSecurity ): SecurityWebFilterChain {
258
+ // @formatter:off
259
+ return http {
260
+ authorizeExchange {
261
+ authorize(anyExchange, authenticated)
262
+ }
263
+ oneTimeTokenLogin {
264
+ tokenGenerationSuccessHandler = TestServerOneTimeTokenGenerationSuccessHandler ()
265
+ }
266
+ }
267
+ }
268
+
269
+ @Bean
270
+ open fun resolver (): ServerGenerateOneTimeTokenRequestResolver {
271
+ val resolver = DefaultServerGenerateOneTimeTokenRequestResolver ()
272
+ return ServerGenerateOneTimeTokenRequestResolver { exchange ->
273
+ resolver.resolve(exchange)
274
+ .map { request -> GenerateOneTimeTokenRequest (request.username, Duration .ofSeconds(600 )) }
275
+ }
276
+ }
277
+ }
278
+
202
279
private class TestServerOneTimeTokenGenerationSuccessHandler : ServerOneTimeTokenGenerationSuccessHandler {
203
280
private var delegate: ServerRedirectOneTimeTokenGenerationSuccessHandler ? = null
204
281
0 commit comments