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

Merge remote-tracking branch 'origin/main' into main

This commit is contained in:
Josh Cummings
2026-04-20 10:22:53 -06:00
6 changed files with 44 additions and 32 deletions
@@ -434,7 +434,8 @@ public class OAuth2ClientRegistrationTests {
assertThat(registeredClient).isNotNull();
assertThat(registeredClient.getTokenSettings().getAccessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
}
@Test
public void requestWhenProtocolRelativeRedirectUriThenBadRequest() throws Exception {
this.spring.register(DefaultValidatorConfiguration.class).autowire();
@@ -529,7 +530,6 @@ public class OAuth2ClientRegistrationTests {
.content(json))
.andReturn();
return registerResult.getResponse().getStatus();
>>>>>>> 7.0.x
}
private OAuth2ClientRegistration registerClient(OAuth2ClientRegistration clientRegistration) throws Exception {
@@ -745,7 +745,7 @@ public class OAuth2ClientRegistrationTests {
authorizationServer
.clientRegistrationEndpoint((clientRegistration) ->
clientRegistration
.authenticationProviders(configureClientRegistrationConverters())
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters()))
)
)
.authorizeHttpRequests((authorize) ->
@@ -21,7 +21,8 @@ import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -141,16 +141,15 @@ public final class OAuth2ClientRegistrationAuthenticationValidator
LOGGER
.debug(LogMessage.format("Invalid request: redirect_uri is not parseable ('%s')", redirectUri));
}
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
throw createException(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
OAuth2ClientMetadataClaimNames.REDIRECT_URIS);
return;
}
if (parsed.getFragment() != null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
LogMessage.format("Invalid request: redirect_uri contains a fragment ('%s')", redirectUri));
}
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
throw createException(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
OAuth2ClientMetadataClaimNames.REDIRECT_URIS);
}
String scheme = parsed.getScheme();
@@ -158,7 +157,7 @@ public final class OAuth2ClientRegistrationAuthenticationValidator
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(LogMessage.format("Invalid request: redirect_uri has no scheme ('%s')", redirectUri));
}
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
throw createException(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
OAuth2ClientMetadataClaimNames.REDIRECT_URIS);
}
if (isUnsafeScheme(scheme)) {
@@ -166,7 +165,7 @@ public final class OAuth2ClientRegistrationAuthenticationValidator
LOGGER.debug(
LogMessage.format("Invalid request: redirect_uri uses unsafe scheme ('%s')", redirectUri));
}
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
throw createException(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
OAuth2ClientMetadataClaimNames.REDIRECT_URIS);
}
}
@@ -184,12 +183,12 @@ public final class OAuth2ClientRegistrationAuthenticationValidator
try {
URI parsed = new URI(redirectUri);
if (parsed.getFragment() != null) {
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
throw createException(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
OAuth2ClientMetadataClaimNames.REDIRECT_URIS);
}
}
catch (URISyntaxException ex) {
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
throw createException(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
OAuth2ClientMetadataClaimNames.REDIRECT_URIS);
}
}
@@ -206,7 +205,7 @@ public final class OAuth2ClientRegistrationAuthenticationValidator
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(LogMessage.format("Invalid request: jwks_uri does not use https ('%s')", jwkSetUrl));
}
throwInvalidClientRegistration("invalid_client_metadata", OAuth2ClientMetadataClaimNames.JWKS_URI);
throw createException("invalid_client_metadata", OAuth2ClientMetadataClaimNames.JWKS_URI);
}
}
@@ -223,7 +222,7 @@ public final class OAuth2ClientRegistrationAuthenticationValidator
LOGGER.debug(LogMessage.format(
"Invalid request: scope must not be set during Dynamic Client Registration ('%s')", scopes));
}
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ClientMetadataClaimNames.SCOPE);
throw createException(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ClientMetadataClaimNames.SCOPE);
}
}
@@ -236,7 +235,7 @@ public final class OAuth2ClientRegistrationAuthenticationValidator
|| "vbscript".equalsIgnoreCase(scheme);
}
private static void throwInvalidClientRegistration(String errorCode, String fieldName) {
private static OAuth2AuthenticationException createException(String errorCode, String fieldName) {
OAuth2Error error = new OAuth2Error(errorCode, "Invalid Client Registration: " + fieldName, ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
@@ -21,7 +21,8 @@ import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationContext;
import org.springframework.util.Assert;
@@ -236,13 +236,6 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
OidcClientRegistration clientRegistrationRequest = clientRegistrationAuthentication.getClientRegistration();
Assert.notNull(clientRegistrationRequest, "clientRegistration cannot be null");
List<String> redirectUris = (clientRegistrationRequest.getRedirectUris() != null)
? clientRegistrationRequest.getRedirectUris() : Collections.emptyList();
if (!isValidRedirectUris(redirectUris)) {
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
OidcClientMetadataClaimNames.REDIRECT_URIS);
}
OidcClientRegistrationAuthenticationContext authenticationContext = OidcClientRegistrationAuthenticationContext
.with(clientRegistrationAuthentication)
.build();
@@ -31,6 +31,7 @@ import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
@@ -144,7 +145,11 @@ public final class OidcClientRegistrationAuthenticationValidator
private static void validateRedirectUris(OidcClientRegistrationAuthenticationContext authenticationContext) {
OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = authenticationContext
.getAuthentication();
Assert.notNull(clientRegistrationAuthentication.getClientRegistration(), "clientRegistration cannot be null");
List<String> redirectUris = clientRegistrationAuthentication.getClientRegistration().getRedirectUris();
if (CollectionUtils.isEmpty(redirectUris)) {
return;
}
validateRedirectUrisStrict(redirectUris, OAuth2ErrorCodes.INVALID_REDIRECT_URI,
OidcClientMetadataClaimNames.REDIRECT_URIS);
}
@@ -153,8 +158,12 @@ public final class OidcClientRegistrationAuthenticationValidator
OidcClientRegistrationAuthenticationContext authenticationContext) {
OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = authenticationContext
.getAuthentication();
Assert.notNull(clientRegistrationAuthentication.getClientRegistration(), "clientRegistration cannot be null");
List<String> postLogoutRedirectUris = clientRegistrationAuthentication.getClientRegistration()
.getPostLogoutRedirectUris();
if (CollectionUtils.isEmpty(postLogoutRedirectUris)) {
return;
}
validateRedirectUrisStrict(postLogoutRedirectUris, "invalid_client_metadata",
OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS);
}
@@ -173,29 +182,28 @@ public final class OidcClientRegistrationAuthenticationValidator
LOGGER.debug(
LogMessage.format("Invalid request: %s is not parseable ('%s')", fieldName, redirectUri));
}
throwInvalidClientRegistration(errorCode, fieldName);
return;
throw createException(errorCode, fieldName);
}
if (parsed.getFragment() != null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(LogMessage.format("Invalid request: %s contains a fragment ('%s')", fieldName,
redirectUri));
}
throwInvalidClientRegistration(errorCode, fieldName);
throw createException(errorCode, fieldName);
}
String scheme = parsed.getScheme();
if (scheme == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(LogMessage.format("Invalid request: %s has no scheme ('%s')", fieldName, redirectUri));
}
throwInvalidClientRegistration(errorCode, fieldName);
throw createException(errorCode, fieldName);
}
if (isUnsafeScheme(scheme)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
LogMessage.format("Invalid request: %s uses unsafe scheme ('%s')", fieldName, redirectUri));
}
throwInvalidClientRegistration(errorCode, fieldName);
throw createException(errorCode, fieldName);
}
}
}
@@ -203,7 +211,11 @@ public final class OidcClientRegistrationAuthenticationValidator
private static void validateRedirectUrisSimple(OidcClientRegistrationAuthenticationContext authenticationContext) {
OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = authenticationContext
.getAuthentication();
Assert.notNull(clientRegistrationAuthentication.getClientRegistration(), "clientRegistration cannot be null");
List<String> redirectUris = clientRegistrationAuthentication.getClientRegistration().getRedirectUris();
if (CollectionUtils.isEmpty(redirectUris)) {
return;
}
validateRedirectUrisFragmentOnly(redirectUris, OAuth2ErrorCodes.INVALID_REDIRECT_URI,
OidcClientMetadataClaimNames.REDIRECT_URIS);
}
@@ -212,8 +224,12 @@ public final class OidcClientRegistrationAuthenticationValidator
OidcClientRegistrationAuthenticationContext authenticationContext) {
OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = authenticationContext
.getAuthentication();
Assert.notNull(clientRegistrationAuthentication.getClientRegistration(), "clientRegistration cannot be null");
List<String> postLogoutRedirectUris = clientRegistrationAuthentication.getClientRegistration()
.getPostLogoutRedirectUris();
if (CollectionUtils.isEmpty(postLogoutRedirectUris)) {
return;
}
validateRedirectUrisFragmentOnly(postLogoutRedirectUris, "invalid_client_metadata",
OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS);
}
@@ -227,11 +243,11 @@ public final class OidcClientRegistrationAuthenticationValidator
try {
URI parsed = new URI(redirectUri);
if (parsed.getFragment() != null) {
throwInvalidClientRegistration(errorCode, fieldName);
throw createException(errorCode, fieldName);
}
}
catch (URISyntaxException ex) {
throwInvalidClientRegistration(errorCode, fieldName);
throw createException(errorCode, fieldName);
}
}
}
@@ -239,6 +255,7 @@ public final class OidcClientRegistrationAuthenticationValidator
private static void validateJwkSetUri(OidcClientRegistrationAuthenticationContext authenticationContext) {
OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = authenticationContext
.getAuthentication();
Assert.notNull(clientRegistrationAuthentication.getClientRegistration(), "clientRegistration cannot be null");
URL jwkSetUrl = clientRegistrationAuthentication.getClientRegistration().getJwkSetUrl();
if (jwkSetUrl == null) {
return;
@@ -247,7 +264,7 @@ public final class OidcClientRegistrationAuthenticationValidator
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(LogMessage.format("Invalid request: jwks_uri does not use https ('%s')", jwkSetUrl));
}
throwInvalidClientRegistration("invalid_client_metadata", OidcClientMetadataClaimNames.JWKS_URI);
throw createException("invalid_client_metadata", OidcClientMetadataClaimNames.JWKS_URI);
}
}
@@ -258,13 +275,14 @@ public final class OidcClientRegistrationAuthenticationValidator
private static void validateScope(OidcClientRegistrationAuthenticationContext authenticationContext) {
OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = authenticationContext
.getAuthentication();
Assert.notNull(clientRegistrationAuthentication.getClientRegistration(), "clientRegistration cannot be null");
List<String> scopes = clientRegistrationAuthentication.getClientRegistration().getScopes();
if (!CollectionUtils.isEmpty(scopes)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(LogMessage.format(
"Invalid request: scope must not be set during Dynamic Client Registration ('%s')", scopes));
}
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_SCOPE, OidcClientMetadataClaimNames.SCOPE);
throw createException(OAuth2ErrorCodes.INVALID_SCOPE, OidcClientMetadataClaimNames.SCOPE);
}
}
@@ -277,7 +295,7 @@ public final class OidcClientRegistrationAuthenticationValidator
|| "vbscript".equalsIgnoreCase(scheme);
}
private static void throwInvalidClientRegistration(String errorCode, String fieldName) {
private static OAuth2AuthenticationException createException(String errorCode, String fieldName) {
OAuth2Error error = new OAuth2Error(errorCode, "Invalid Client Registration: " + fieldName, ERROR_URI);
throw new OAuth2AuthenticationException(error);
}