Skip to content

Commit 5756b11

Browse files
authored
Merge branch 'spring-projects:main' into main
2 parents e876fe9 + b6c813c commit 5756b11

File tree

8 files changed

+247
-67
lines changed

8 files changed

+247
-67
lines changed

core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,10 +17,12 @@
1717
package org.springframework.security.authentication.ott;
1818

1919
import org.springframework.security.authentication.AuthenticationProvider;
20+
import org.springframework.security.authentication.BadCredentialsException;
2021
import org.springframework.security.core.Authentication;
2122
import org.springframework.security.core.AuthenticationException;
2223
import org.springframework.security.core.userdetails.UserDetails;
2324
import org.springframework.security.core.userdetails.UserDetailsService;
25+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
2426
import org.springframework.util.Assert;
2527

2628
/**
@@ -52,11 +54,16 @@ public Authentication authenticate(Authentication authentication) throws Authent
5254
if (consumed == null) {
5355
throw new InvalidOneTimeTokenException("Invalid token");
5456
}
55-
UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
56-
OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user,
57-
user.getAuthorities());
58-
authenticated.setDetails(otpAuthenticationToken.getDetails());
59-
return authenticated;
57+
try {
58+
UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
59+
OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user,
60+
user.getAuthorities());
61+
authenticated.setDetails(otpAuthenticationToken.getDetails());
62+
return authenticated;
63+
}
64+
catch (UsernameNotFoundException ex) {
65+
throw new BadCredentialsException("Failed to authenticate the one-time token");
66+
}
6067
}
6168

6269
@Override
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.authentication.ott;
18+
19+
import java.time.Instant;
20+
import java.util.List;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
import org.mockito.InjectMocks;
25+
import org.mockito.Mock;
26+
import org.mockito.junit.jupiter.MockitoExtension;
27+
28+
import org.springframework.security.authentication.BadCredentialsException;
29+
import org.springframework.security.core.userdetails.User;
30+
import org.springframework.security.core.userdetails.UserDetailsService;
31+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
32+
import org.springframework.util.CollectionUtils;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
36+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
37+
import static org.mockito.ArgumentMatchers.any;
38+
import static org.mockito.ArgumentMatchers.anyString;
39+
import static org.mockito.BDDMockito.given;
40+
41+
/**
42+
* Tests for {@link OneTimeTokenAuthenticationProvider}.
43+
*
44+
* @author Max Batischev
45+
*/
46+
@ExtendWith(MockitoExtension.class)
47+
public class OneTimeTokenAuthenticationProviderTests {
48+
49+
private static final String TOKEN = "token";
50+
51+
private static final String USERNAME = "Max";
52+
53+
private static final String PASSWORD = "password";
54+
55+
@Mock
56+
private OneTimeTokenService oneTimeTokenService;
57+
58+
@Mock
59+
private UserDetailsService userDetailsService;
60+
61+
@InjectMocks
62+
private OneTimeTokenAuthenticationProvider provider;
63+
64+
@Test
65+
void authenticateWhenAuthenticationTokenIsPresentThenAuthenticates() {
66+
given(this.oneTimeTokenService.consume(any()))
67+
.willReturn(new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now().plusSeconds(120)));
68+
given(this.userDetailsService.loadUserByUsername(anyString()))
69+
.willReturn(new User(USERNAME, PASSWORD, List.of()));
70+
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
71+
72+
OneTimeTokenAuthenticationToken authentication = (OneTimeTokenAuthenticationToken) this.provider
73+
.authenticate(token);
74+
75+
User user = (User) authentication.getPrincipal();
76+
assertThat(authentication.isAuthenticated()).isTrue();
77+
assertThat(user.getUsername()).isEqualTo(USERNAME);
78+
assertThat(user.getPassword()).isEqualTo(PASSWORD);
79+
assertThat(CollectionUtils.isEmpty(user.getAuthorities())).isTrue();
80+
}
81+
82+
@Test
83+
void authenticateWhenOneTimeTokenIsNotFoundThenFails() {
84+
given(this.oneTimeTokenService.consume(any())).willReturn(null);
85+
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
86+
87+
assertThatExceptionOfType(InvalidOneTimeTokenException.class)
88+
.isThrownBy(() -> this.provider.authenticate(token));
89+
}
90+
91+
@Test
92+
void authenticateWhenUserIsNotFoundThenFails() {
93+
given(this.oneTimeTokenService.consume(any()))
94+
.willReturn(new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now().plusSeconds(120)));
95+
given(this.userDetailsService.loadUserByUsername(anyString())).willThrow(UsernameNotFoundException.class);
96+
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
97+
98+
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.provider.authenticate(token));
99+
}
100+
101+
@Test
102+
void constructorWhenOneTimeTokenServiceIsNullThenThrowIllegalArgumentException() {
103+
// @formatter:off
104+
assertThatIllegalArgumentException()
105+
.isThrownBy(() -> new OneTimeTokenAuthenticationProvider(null, this.userDetailsService))
106+
.withMessage("oneTimeTokenService cannot be null");
107+
// @formatter:on
108+
}
109+
110+
@Test
111+
void constructorWhenUserDetailsServiceIsNullThenThrowIllegalArgumentException() {
112+
// @formatter:off
113+
assertThatIllegalArgumentException()
114+
.isThrownBy(() -> new OneTimeTokenAuthenticationProvider(this.oneTimeTokenService, null))
115+
.withMessage("userDetailsService cannot be null");
116+
// @formatter:on
117+
}
118+
119+
}

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.security.oauth2.client.registration;
1818

1919
import java.net.URI;
20-
import java.util.Collections;
2120
import java.util.LinkedHashMap;
2221
import java.util.List;
2322
import java.util.Map;
@@ -37,6 +36,7 @@
3736
import org.springframework.util.Assert;
3837
import org.springframework.web.client.HttpClientErrorException;
3938
import org.springframework.web.client.RestTemplate;
39+
import org.springframework.web.util.UriComponents;
4040
import org.springframework.web.util.UriComponentsBuilder;
4141

4242
/**
@@ -145,7 +145,7 @@ public static ClientRegistration.Builder fromOidcConfiguration(Map<String, Objec
145145
*/
146146
public static ClientRegistration.Builder fromOidcIssuerLocation(String issuer) {
147147
Assert.hasText(issuer, "issuer cannot be empty");
148-
return getBuilder(issuer, oidc(URI.create(issuer)));
148+
return getBuilder(issuer, oidc(issuer));
149149
}
150150

151151
/**
@@ -188,21 +188,17 @@ public static ClientRegistration.Builder fromOidcIssuerLocation(String issuer) {
188188
*/
189189
public static ClientRegistration.Builder fromIssuerLocation(String issuer) {
190190
Assert.hasText(issuer, "issuer cannot be empty");
191-
URI uri = URI.create(issuer);
192-
return getBuilder(issuer, oidc(uri), oidcRfc8414(uri), oauth(uri));
191+
return getBuilder(issuer, oidc(issuer), oidcRfc8414(issuer), oauth(issuer));
193192
}
194193

195-
private static Supplier<ClientRegistration.Builder> oidc(URI issuer) {
196-
// @formatter:off
197-
URI uri = UriComponentsBuilder.fromUri(issuer)
198-
.replacePath(issuer.getPath() + OIDC_METADATA_PATH)
199-
.build(Collections.emptyMap());
194+
static Supplier<ClientRegistration.Builder> oidc(String issuer) {
195+
UriComponents uri = oidcUri(issuer);
200196
// @formatter:on
201197
return () -> {
202-
RequestEntity<Void> request = RequestEntity.get(uri).build();
198+
RequestEntity<Void> request = RequestEntity.get(uri.toUriString()).build();
203199
Map<String, Object> configuration = rest.exchange(request, typeReference).getBody();
204200
OIDCProviderMetadata metadata = parse(configuration, OIDCProviderMetadata::parse);
205-
ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer.toASCIIString())
201+
ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer)
206202
.jwkSetUri(metadata.getJWKSetURI().toASCIIString());
207203
if (metadata.getUserInfoEndpointURI() != null) {
208204
builder.userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString());
@@ -211,30 +207,48 @@ private static Supplier<ClientRegistration.Builder> oidc(URI issuer) {
211207
};
212208
}
213209

214-
private static Supplier<ClientRegistration.Builder> oidcRfc8414(URI issuer) {
210+
static UriComponents oidcUri(String issuer) {
211+
UriComponents uri = UriComponentsBuilder.fromUriString(issuer).build();
215212
// @formatter:off
216-
URI uri = UriComponentsBuilder.fromUri(issuer)
217-
.replacePath(OIDC_METADATA_PATH + issuer.getPath())
218-
.build(Collections.emptyMap());
213+
return UriComponentsBuilder.newInstance().uriComponents(uri)
214+
.replacePath(uri.getPath() + OIDC_METADATA_PATH)
215+
.build();
216+
}
217+
218+
static Supplier<ClientRegistration.Builder> oidcRfc8414(String issuer) {
219+
UriComponents uri = oidcRfc8414Uri(issuer);
219220
// @formatter:on
220221
return getRfc8414Builder(issuer, uri);
221222
}
222223

223-
private static Supplier<ClientRegistration.Builder> oauth(URI issuer) {
224+
static UriComponents oidcRfc8414Uri(String issuer) {
225+
UriComponents uri = UriComponentsBuilder.fromUriString(issuer).build();
224226
// @formatter:off
225-
URI uri = UriComponentsBuilder.fromUri(issuer)
226-
.replacePath(OAUTH_METADATA_PATH + issuer.getPath())
227-
.build(Collections.emptyMap());
228-
// @formatter:on
227+
return UriComponentsBuilder.newInstance().uriComponents(uri)
228+
.replacePath(OIDC_METADATA_PATH + uri.getPath())
229+
.build();
230+
}
231+
232+
static Supplier<ClientRegistration.Builder> oauth(String issuer) {
233+
UriComponents uri = oauthUri(issuer);
229234
return getRfc8414Builder(issuer, uri);
230235
}
231236

232-
private static Supplier<ClientRegistration.Builder> getRfc8414Builder(URI issuer, URI uri) {
237+
static UriComponents oauthUri(String issuer) {
238+
UriComponents uri = UriComponentsBuilder.fromUriString(issuer).build();
239+
// @formatter:off
240+
return UriComponentsBuilder.newInstance().uriComponents(uri)
241+
.replacePath(OAUTH_METADATA_PATH + uri.getPath())
242+
.build();
243+
// @formatter:on
244+
}
245+
246+
private static Supplier<ClientRegistration.Builder> getRfc8414Builder(String issuer, UriComponents uri) {
233247
return () -> {
234-
RequestEntity<Void> request = RequestEntity.get(uri).build();
248+
RequestEntity<Void> request = RequestEntity.get(uri.toUriString()).build();
235249
Map<String, Object> configuration = rest.exchange(request, typeReference).getBody();
236250
AuthorizationServerMetadata metadata = parse(configuration, AuthorizationServerMetadata::parse);
237-
ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer.toASCIIString());
251+
ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer);
238252
URI jwkSetUri = metadata.getJWKSetURI();
239253
if (jwkSetUri != null) {
240254
builder.jwkSetUri(jwkSetUri.toASCIIString());

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.http.MediaType;
3535
import org.springframework.security.oauth2.core.AuthorizationGrantType;
3636
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
37+
import org.springframework.web.util.UriComponents;
3738

3839
import static org.assertj.core.api.Assertions.assertThat;
3940
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -569,6 +570,17 @@ public void issuerWhenOidcConfigurationTlsClientAuthMethodThenSuccess() throws E
569570
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
570571
}
571572

573+
// gh-15852
574+
@Test
575+
public void oidcWhenHostContainsUnderscoreThenRetains() {
576+
UriComponents oidc = ClientRegistrations.oidcUri("https://elated_sutherland:8080/path");
577+
assertThat(oidc.getHost()).isEqualTo("elated_sutherland");
578+
UriComponents oauth = ClientRegistrations.oauthUri("https://elated_sutherland:8080/path");
579+
assertThat(oauth.getHost()).isEqualTo("elated_sutherland");
580+
UriComponents oidcRfc8414 = ClientRegistrations.oidcRfc8414Uri("https://elated_sutherland:8080/path");
581+
assertThat(oidcRfc8414.getHost()).isEqualTo("elated_sutherland");
582+
}
583+
572584
private ClientRegistration.Builder registration(String path) throws Exception {
573585
this.issuer = createIssuerFromServer(path);
574586
this.response.put("issuer", this.issuer);

oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoderProviderConfigurationUtils.java

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.security.oauth2.jwt;
1818

19-
import java.net.URI;
20-
import java.util.Collections;
2119
import java.util.HashSet;
2220
import java.util.List;
2321
import java.util.Map;
@@ -45,6 +43,7 @@
4543
import org.springframework.web.client.HttpClientErrorException;
4644
import org.springframework.web.client.RestOperations;
4745
import org.springframework.web.client.RestTemplate;
46+
import org.springframework.web.util.UriComponents;
4847
import org.springframework.web.util.UriComponentsBuilder;
4948

5049
/**
@@ -82,12 +81,11 @@ private JwtDecoderProviderConfigurationUtils() {
8281
}
8382

8483
static Map<String, Object> getConfigurationForOidcIssuerLocation(String oidcIssuerLocation) {
85-
return getConfiguration(oidcIssuerLocation, rest, oidc(URI.create(oidcIssuerLocation)));
84+
return getConfiguration(oidcIssuerLocation, rest, oidc(oidcIssuerLocation));
8685
}
8786

8887
static Map<String, Object> getConfigurationForIssuerLocation(String issuer, RestOperations rest) {
89-
URI uri = URI.create(issuer);
90-
return getConfiguration(issuer, rest, oidc(uri), oidcRfc8414(uri), oauth(uri));
88+
return getConfiguration(issuer, rest, oidc(issuer), oidcRfc8414(issuer), oauth(issuer));
9189
}
9290

9391
static Map<String, Object> getConfigurationForIssuerLocation(String issuer) {
@@ -159,11 +157,11 @@ private static String getMetadataIssuer(Map<String, Object> configuration) {
159157
return "(unavailable)";
160158
}
161159

162-
private static Map<String, Object> getConfiguration(String issuer, RestOperations rest, URI... uris) {
160+
private static Map<String, Object> getConfiguration(String issuer, RestOperations rest, UriComponents... uris) {
163161
String errorMessage = "Unable to resolve the Configuration with the provided Issuer of " + "\"" + issuer + "\"";
164-
for (URI uri : uris) {
162+
for (UriComponents uri : uris) {
165163
try {
166-
RequestEntity<Void> request = RequestEntity.get(uri).build();
164+
RequestEntity<Void> request = RequestEntity.get(uri.toUriString()).build();
167165
ResponseEntity<Map<String, Object>> response = rest.exchange(request, STRING_OBJECT_MAP);
168166
Map<String, Object> configuration = response.getBody();
169167
Assert.isTrue(configuration.get("jwks_uri") != null, "The public JWK set URI must not be null");
@@ -183,27 +181,30 @@ private static Map<String, Object> getConfiguration(String issuer, RestOperation
183181
throw new IllegalArgumentException(errorMessage);
184182
}
185183

186-
private static URI oidc(URI issuer) {
184+
static UriComponents oidc(String issuer) {
185+
UriComponents uri = UriComponentsBuilder.fromUriString(issuer).build();
187186
// @formatter:off
188-
return UriComponentsBuilder.fromUri(issuer)
189-
.replacePath(issuer.getPath() + OIDC_METADATA_PATH)
190-
.build(Collections.emptyMap());
187+
return UriComponentsBuilder.newInstance().uriComponents(uri)
188+
.replacePath(uri.getPath() + OIDC_METADATA_PATH)
189+
.build();
191190
// @formatter:on
192191
}
193192

194-
private static URI oidcRfc8414(URI issuer) {
193+
static UriComponents oidcRfc8414(String issuer) {
194+
UriComponents uri = UriComponentsBuilder.fromUriString(issuer).build();
195195
// @formatter:off
196-
return UriComponentsBuilder.fromUri(issuer)
197-
.replacePath(OIDC_METADATA_PATH + issuer.getPath())
198-
.build(Collections.emptyMap());
196+
return UriComponentsBuilder.newInstance().uriComponents(uri)
197+
.replacePath(OIDC_METADATA_PATH + uri.getPath())
198+
.build();
199199
// @formatter:on
200200
}
201201

202-
private static URI oauth(URI issuer) {
202+
static UriComponents oauth(String issuer) {
203+
UriComponents uri = UriComponentsBuilder.fromUriString(issuer).build();
203204
// @formatter:off
204-
return UriComponentsBuilder.fromUri(issuer)
205-
.replacePath(OAUTH_METADATA_PATH + issuer.getPath())
206-
.build(Collections.emptyMap());
205+
return UriComponentsBuilder.newInstance().uriComponents(uri)
206+
.replacePath(OAUTH_METADATA_PATH + uri.getPath())
207+
.build();
207208
// @formatter:on
208209
}
209210

0 commit comments

Comments
 (0)