diff --git a/docs/src/docs/asciidoc/protocol-endpoints.adoc b/docs/src/docs/asciidoc/protocol-endpoints.adoc
index 0be400bf..56347119 100644
--- a/docs/src/docs/asciidoc/protocol-endpoints.adoc
+++ b/docs/src/docs/asciidoc/protocol-endpoints.adoc
@@ -369,7 +369,8 @@ The OpenID Connect 1.0 Client Registration endpoint is disabled by default becau
`OidcClientRegistrationEndpointFilter` is configured with the following defaults:
-* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcClientRegistrationAuthenticationProvider`.
+* `*AuthenticationConverter*` -- An `OidcClientRegistrationAuthenticationConverter`.
+* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcClientRegistrationAuthenticationProvider` and `OidcClientConfigurationAuthenticationProvider`.
The OpenID Connect 1.0 Client Registration endpoint is an https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[OAuth2 protected resource], which *REQUIRES* an access token to be sent as a bearer token in the Client Registration (or Client Read) request.
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java
index 3f65e921..d631f85a 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java
@@ -19,6 +19,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
@@ -59,6 +60,12 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity));
httpSecurity.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider));
+
+ OidcClientConfigurationAuthenticationProvider oidcClientConfigurationAuthenticationProvider =
+ new OidcClientConfigurationAuthenticationProvider(
+ OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
+ OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
+ httpSecurity.authenticationProvider(postProcess(oidcClientConfigurationAuthenticationProvider));
}
@Override
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java
new file mode 100644
index 00000000..575e4593
--- /dev/null
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2020-2022 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.oauth2.server.authorization.oidc.authentication;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 Dynamic Client Configuration Endpoint.
+ *
+ * @author Ovidiu Popa
+ * @author Joe Grandja
+ * @author Rafal Lewczuk
+ * @since 0.4.0
+ * @see RegisteredClientRepository
+ * @see OAuth2AuthorizationService
+ * @see OidcClientRegistrationAuthenticationProvider
+ * @see 4. Client Configuration Endpoint
+ */
+public final class OidcClientConfigurationAuthenticationProvider implements AuthenticationProvider {
+ static final String DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE = "client.read";
+ private final RegisteredClientRepository registeredClientRepository;
+ private final OAuth2AuthorizationService authorizationService;
+ private final Converter clientRegistrationConverter;
+
+ /**
+ * Constructs an {@code OidcClientConfigurationAuthenticationProvider} using the provided parameters.
+ *
+ * @param registeredClientRepository the repository of registered clients
+ * @param authorizationService the authorization service
+ */
+ public OidcClientConfigurationAuthenticationProvider(RegisteredClientRepository registeredClientRepository,
+ OAuth2AuthorizationService authorizationService) {
+ Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
+ Assert.notNull(authorizationService, "authorizationService cannot be null");
+ this.registeredClientRepository = registeredClientRepository;
+ this.authorizationService = authorizationService;
+ this.clientRegistrationConverter = new OidcClientRegistrationConverter();
+ }
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication =
+ (OidcClientRegistrationAuthenticationToken) authentication;
+
+ if (!StringUtils.hasText(clientRegistrationAuthentication.getClientId())) {
+ // This is not a Client Configuration Request.
+ // Return null to allow OidcClientRegistrationAuthenticationProvider to handle it.
+ return null;
+ }
+
+ // Validate the "registration" access token
+ AbstractOAuth2TokenAuthenticationToken> accessTokenAuthentication = null;
+ if (AbstractOAuth2TokenAuthenticationToken.class.isAssignableFrom(clientRegistrationAuthentication.getPrincipal().getClass())) {
+ accessTokenAuthentication = (AbstractOAuth2TokenAuthenticationToken>) clientRegistrationAuthentication.getPrincipal();
+ }
+ if (accessTokenAuthentication == null || !accessTokenAuthentication.isAuthenticated()) {
+ throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
+ }
+
+ String accessTokenValue = accessTokenAuthentication.getToken().getTokenValue();
+ OAuth2Authorization authorization = this.authorizationService.findByToken(
+ accessTokenValue, OAuth2TokenType.ACCESS_TOKEN);
+ if (authorization == null) {
+ throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
+ }
+
+ OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken();
+ if (!authorizedAccessToken.isActive()) {
+ throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
+ }
+ checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE));
+
+ return findRegistration(clientRegistrationAuthentication, authorization);
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return OidcClientRegistrationAuthenticationToken.class.isAssignableFrom(authentication);
+ }
+
+ private OidcClientRegistrationAuthenticationToken findRegistration(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
+ OAuth2Authorization authorization) {
+
+ RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
+ clientRegistrationAuthentication.getClientId());
+ if (registeredClient == null) {
+ throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
+ }
+
+ if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) {
+ throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
+ }
+
+ OidcClientRegistration clientRegistration = this.clientRegistrationConverter.convert(registeredClient);
+
+ return new OidcClientRegistrationAuthenticationToken(
+ (Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void checkScope(OAuth2Authorization.Token authorizedAccessToken, Set requiredScope) {
+ Collection authorizedScope = Collections.emptySet();
+ if (authorizedAccessToken.getClaims().containsKey(OAuth2ParameterNames.SCOPE)) {
+ authorizedScope = (Collection) authorizedAccessToken.getClaims().get(OAuth2ParameterNames.SCOPE);
+ }
+ if (!authorizedScope.containsAll(requiredScope)) {
+ throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
+ } else if (authorizedScope.size() != requiredScope.size()) {
+ // Restrict the access token to only contain the required scope
+ throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
+ }
+ }
+
+}
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java
index 8880e605..e7ec6233 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java
@@ -23,9 +23,11 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -49,7 +51,6 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
@@ -62,10 +63,9 @@ import org.springframework.security.oauth2.server.resource.authentication.Abstra
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
-import org.springframework.web.util.UriComponentsBuilder;
/**
- * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 Dynamic Client Registration (and Configuration) Endpoint.
+ * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 Dynamic Client Registration Endpoint.
*
* @author Ovidiu Popa
* @author Joe Grandja
@@ -74,20 +74,17 @@ import org.springframework.web.util.UriComponentsBuilder;
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
* @see OAuth2TokenGenerator
+ * @see OidcClientConfigurationAuthenticationProvider
* @see 3. Client Registration Endpoint
- * @see 4. Client Configuration Endpoint
*/
public final class OidcClientRegistrationAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError";
- private static final StringKeyGenerator CLIENT_ID_GENERATOR = new Base64StringKeyGenerator(
- Base64.getUrlEncoder().withoutPadding(), 32);
- private static final StringKeyGenerator CLIENT_SECRET_GENERATOR = new Base64StringKeyGenerator(
- Base64.getUrlEncoder().withoutPadding(), 48);
private static final String DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE = "client.create";
- private static final String DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE = "client.read";
private final RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator extends OAuth2Token> tokenGenerator;
+ private final Converter clientRegistrationConverter;
+ private final Converter registeredClientConverter;
/**
* Constructs an {@code OidcClientRegistrationAuthenticationProvider} using the provided parameters.
@@ -105,6 +102,8 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
this.registeredClientRepository = registeredClientRepository;
this.authorizationService = authorizationService;
this.tokenGenerator = tokenGenerator;
+ this.clientRegistrationConverter = new OidcClientRegistrationConverter();
+ this.registeredClientConverter = new RegisteredClientConverter();
}
@Override
@@ -112,7 +111,13 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication =
(OidcClientRegistrationAuthenticationToken) authentication;
- // Validate the "initial" or "registration" access token
+ if (clientRegistrationAuthentication.getClientRegistration() == null) {
+ // This is not a Client Registration Request.
+ // Return null to allow OidcClientConfigurationAuthenticationProvider to handle it.
+ return null;
+ }
+
+ // Validate the "initial" access token
AbstractOAuth2TokenAuthenticationToken> accessTokenAuthentication = null;
if (AbstractOAuth2TokenAuthenticationToken.class.isAssignableFrom(clientRegistrationAuthentication.getPrincipal().getClass())) {
accessTokenAuthentication = (AbstractOAuth2TokenAuthenticationToken>) clientRegistrationAuthentication.getPrincipal();
@@ -122,7 +127,6 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
}
String accessTokenValue = accessTokenAuthentication.getToken().getTokenValue();
-
OAuth2Authorization authorization = this.authorizationService.findByToken(
accessTokenValue, OAuth2TokenType.ACCESS_TOKEN);
if (authorization == null) {
@@ -133,10 +137,9 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
if (!authorizedAccessToken.isActive()) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
}
+ checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE));
- return clientRegistrationAuthentication.getClientRegistration() != null ?
- registerClient(clientRegistrationAuthentication, authorization) :
- findRegistration(clientRegistrationAuthentication, authorization);
+ return registerClient(clientRegistrationAuthentication, authorization);
}
@Override
@@ -144,34 +147,9 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
return OidcClientRegistrationAuthenticationToken.class.isAssignableFrom(authentication);
}
- private OidcClientRegistrationAuthenticationToken findRegistration(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
- OAuth2Authorization authorization) {
-
- OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken();
- checkScopeForConfiguration(authorizedAccessToken);
-
- RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
- clientRegistrationAuthentication.getClientId());
- if (registeredClient == null) {
- throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
- }
-
- if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) {
- throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
- }
-
- OidcClientRegistration clientRegistration = buildRegistration(registeredClient).build();
-
- return new OidcClientRegistrationAuthenticationToken(
- (Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration);
- }
-
private OidcClientRegistrationAuthenticationToken registerClient(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
OAuth2Authorization authorization) {
- OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken();
- checkScopeForRegistration(authorizedAccessToken);
-
if (!isValidRedirectUris(clientRegistrationAuthentication.getClientRegistration().getRedirectUris())) {
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI, OidcClientMetadataClaimNames.REDIRECT_URIS);
}
@@ -180,19 +158,20 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
throwInvalidClientRegistration("invalid_client_metadata", OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD);
}
- RegisteredClient registeredClient = createClient(clientRegistrationAuthentication.getClientRegistration());
+ RegisteredClient registeredClient = this.registeredClientConverter.convert(clientRegistrationAuthentication.getClientRegistration());
this.registeredClientRepository.save(registeredClient);
OAuth2Authorization registeredClientAuthorization = registerAccessToken(registeredClient);
// Invalidate the "initial" access token as it can only be used once
- authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorizedAccessToken.getToken());
+ authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorization.getAccessToken().getToken());
if (authorization.getRefreshToken() != null) {
authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorization.getRefreshToken().getToken());
}
this.authorizationService.save(authorization);
- OidcClientRegistration clientRegistration = buildRegistration(registeredClient)
+ Map clientRegistrationClaims = this.clientRegistrationConverter.convert(registeredClient).getClaims();
+ OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(clientRegistrationClaims)
.registrationAccessToken(registeredClientAuthorization.getAccessToken().getToken().getTokenValue())
.build();
@@ -205,7 +184,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
registeredClient.getClientAuthenticationMethods().iterator().next(), registeredClient.getClientSecret());
Set authorizedScopes = new HashSet<>();
- authorizedScopes.add(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE);
+ authorizedScopes.add(OidcClientConfigurationAuthenticationProvider.DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE);
authorizedScopes = Collections.unmodifiableSet(authorizedScopes);
// @formatter:off
@@ -249,66 +228,6 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
return authorization;
}
- private OidcClientRegistration.Builder buildRegistration(RegisteredClient registeredClient) {
- // @formatter:off
- OidcClientRegistration.Builder builder = OidcClientRegistration.builder()
- .clientId(registeredClient.getClientId())
- .clientIdIssuedAt(registeredClient.getClientIdIssuedAt())
- .clientName(registeredClient.getClientName());
-
- if (registeredClient.getClientSecret() != null) {
- builder.clientSecret(registeredClient.getClientSecret());
- }
-
- builder.redirectUris(redirectUris ->
- redirectUris.addAll(registeredClient.getRedirectUris()));
-
- builder.grantTypes(grantTypes ->
- registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
- grantTypes.add(authorizationGrantType.getValue())));
-
- if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
- builder.responseType(OAuth2AuthorizationResponseType.CODE.getValue());
- }
-
- if (!CollectionUtils.isEmpty(registeredClient.getScopes())) {
- builder.scopes(scopes ->
- scopes.addAll(registeredClient.getScopes()));
- }
-
- AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
- String registrationClientUri = UriComponentsBuilder.fromUriString(authorizationServerContext.getIssuer())
- .path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint())
- .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
- .toUriString();
-
- builder
- .tokenEndpointAuthenticationMethod(registeredClient.getClientAuthenticationMethods().iterator().next().getValue())
- .idTokenSignedResponseAlgorithm(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName())
- .registrationClientUrl(registrationClientUri);
-
- ClientSettings clientSettings = registeredClient.getClientSettings();
-
- if (clientSettings.getJwkSetUrl() != null) {
- builder.jwkSetUrl(clientSettings.getJwkSetUrl());
- }
-
- if (clientSettings.getTokenEndpointAuthenticationSigningAlgorithm() != null) {
- builder.tokenEndpointAuthenticationSigningAlgorithm(clientSettings.getTokenEndpointAuthenticationSigningAlgorithm().getName());
- }
-
- return builder;
- // @formatter:on
- }
-
- private static void checkScopeForRegistration(OAuth2Authorization.Token authorizedAccessToken) {
- checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE));
- }
-
- private static void checkScopeForConfiguration(OAuth2Authorization.Token authorizedAccessToken) {
- checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE));
- }
-
@SuppressWarnings("unchecked")
private static void checkScope(OAuth2Authorization.Token authorizedAccessToken, Set requiredScope) {
Collection authorizedScope = Collections.emptySet();
@@ -366,78 +285,6 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
}
}
- private static RegisteredClient createClient(OidcClientRegistration clientRegistration) {
- // @formatter:off
- RegisteredClient.Builder builder = RegisteredClient.withId(UUID.randomUUID().toString())
- .clientId(CLIENT_ID_GENERATOR.generateKey())
- .clientIdIssuedAt(Instant.now())
- .clientName(clientRegistration.getClientName());
-
- if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
- builder
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
- .clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
- } else if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
- builder
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
- .clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
- } else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
- builder.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
- } else {
- builder
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
- .clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
- }
-
- builder.redirectUris(redirectUris ->
- redirectUris.addAll(clientRegistration.getRedirectUris()));
-
- if (!CollectionUtils.isEmpty(clientRegistration.getGrantTypes())) {
- builder.authorizationGrantTypes(authorizationGrantTypes ->
- clientRegistration.getGrantTypes().forEach(grantType ->
- authorizationGrantTypes.add(new AuthorizationGrantType(grantType))));
- } else {
- builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
- }
- if (CollectionUtils.isEmpty(clientRegistration.getResponseTypes()) ||
- clientRegistration.getResponseTypes().contains(OAuth2AuthorizationResponseType.CODE.getValue())) {
- builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
- }
-
- if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
- builder.scopes(scopes ->
- scopes.addAll(clientRegistration.getScopes()));
- }
-
- ClientSettings.Builder clientSettingsBuilder = ClientSettings.builder()
- .requireProofKey(true)
- .requireAuthorizationConsent(true);
-
- if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
- MacAlgorithm macAlgorithm = MacAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm());
- if (macAlgorithm == null) {
- macAlgorithm = MacAlgorithm.HS256;
- }
- clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(macAlgorithm);
- } else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
- SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm());
- if (signatureAlgorithm == null) {
- signatureAlgorithm = SignatureAlgorithm.RS256;
- }
- clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(signatureAlgorithm);
- clientSettingsBuilder.jwkSetUrl(clientRegistration.getJwkSetUrl().toString());
- }
-
- builder
- .clientSettings(clientSettingsBuilder.build())
- .tokenSettings(TokenSettings.builder()
- .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
- .build());
-
- return builder.build();
- // @formatter:on
- }
-
private static void throwInvalidClientRegistration(String errorCode, String fieldName) {
OAuth2Error error = new OAuth2Error(
errorCode,
@@ -446,4 +293,84 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
throw new OAuth2AuthenticationException(error);
}
+ private static final class RegisteredClientConverter implements Converter {
+ private static final StringKeyGenerator CLIENT_ID_GENERATOR = new Base64StringKeyGenerator(
+ Base64.getUrlEncoder().withoutPadding(), 32);
+ private static final StringKeyGenerator CLIENT_SECRET_GENERATOR = new Base64StringKeyGenerator(
+ Base64.getUrlEncoder().withoutPadding(), 48);
+
+ @Override
+ public RegisteredClient convert(OidcClientRegistration clientRegistration) {
+ // @formatter:off
+ RegisteredClient.Builder builder = RegisteredClient.withId(UUID.randomUUID().toString())
+ .clientId(CLIENT_ID_GENERATOR.generateKey())
+ .clientIdIssuedAt(Instant.now())
+ .clientName(clientRegistration.getClientName());
+
+ if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
+ builder
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
+ .clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
+ } else if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
+ builder
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
+ .clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
+ } else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
+ builder.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
+ } else {
+ builder
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+ .clientSecret(CLIENT_SECRET_GENERATOR.generateKey());
+ }
+
+ builder.redirectUris(redirectUris ->
+ redirectUris.addAll(clientRegistration.getRedirectUris()));
+
+ if (!CollectionUtils.isEmpty(clientRegistration.getGrantTypes())) {
+ builder.authorizationGrantTypes(authorizationGrantTypes ->
+ clientRegistration.getGrantTypes().forEach(grantType ->
+ authorizationGrantTypes.add(new AuthorizationGrantType(grantType))));
+ } else {
+ builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
+ }
+ if (CollectionUtils.isEmpty(clientRegistration.getResponseTypes()) ||
+ clientRegistration.getResponseTypes().contains(OAuth2AuthorizationResponseType.CODE.getValue())) {
+ builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
+ }
+
+ if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
+ builder.scopes(scopes ->
+ scopes.addAll(clientRegistration.getScopes()));
+ }
+
+ ClientSettings.Builder clientSettingsBuilder = ClientSettings.builder()
+ .requireProofKey(true)
+ .requireAuthorizationConsent(true);
+
+ if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
+ MacAlgorithm macAlgorithm = MacAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm());
+ if (macAlgorithm == null) {
+ macAlgorithm = MacAlgorithm.HS256;
+ }
+ clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(macAlgorithm);
+ } else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) {
+ SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm());
+ if (signatureAlgorithm == null) {
+ signatureAlgorithm = SignatureAlgorithm.RS256;
+ }
+ clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(signatureAlgorithm);
+ clientSettingsBuilder.jwkSetUrl(clientRegistration.getJwkSetUrl().toString());
+ }
+
+ builder
+ .clientSettings(clientSettingsBuilder.build())
+ .tokenSettings(TokenSettings.builder()
+ .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
+ .build());
+
+ return builder.build();
+ // @formatter:on
+ }
+
+ }
}
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java
index 89cb02fe..c14f9201 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java
@@ -33,6 +33,7 @@ import org.springframework.util.Assert;
* @see AbstractAuthenticationToken
* @see OidcClientRegistration
* @see OidcClientRegistrationAuthenticationProvider
+ * @see OidcClientConfigurationAuthenticationProvider
*/
public class OidcClientRegistrationAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationConverter.java
new file mode 100644
index 00000000..b7e16d4e
--- /dev/null
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationConverter.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020-2022 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.oauth2.server.authorization.oidc.authentication;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
+import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * @author Joe Grandja
+ * @since 0.4.0
+ */
+final class OidcClientRegistrationConverter implements Converter {
+
+ @Override
+ public OidcClientRegistration convert(RegisteredClient registeredClient) {
+ // @formatter:off
+ OidcClientRegistration.Builder builder = OidcClientRegistration.builder()
+ .clientId(registeredClient.getClientId())
+ .clientIdIssuedAt(registeredClient.getClientIdIssuedAt())
+ .clientName(registeredClient.getClientName());
+
+ if (registeredClient.getClientSecret() != null) {
+ builder.clientSecret(registeredClient.getClientSecret());
+ }
+
+ builder.redirectUris(redirectUris ->
+ redirectUris.addAll(registeredClient.getRedirectUris()));
+
+ builder.grantTypes(grantTypes ->
+ registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
+ grantTypes.add(authorizationGrantType.getValue())));
+
+ if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
+ builder.responseType(OAuth2AuthorizationResponseType.CODE.getValue());
+ }
+
+ if (!CollectionUtils.isEmpty(registeredClient.getScopes())) {
+ builder.scopes(scopes ->
+ scopes.addAll(registeredClient.getScopes()));
+ }
+
+ AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
+ String registrationClientUri = UriComponentsBuilder.fromUriString(authorizationServerContext.getIssuer())
+ .path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint())
+ .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
+ .toUriString();
+
+ builder
+ .tokenEndpointAuthenticationMethod(registeredClient.getClientAuthenticationMethods().iterator().next().getValue())
+ .idTokenSignedResponseAlgorithm(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName())
+ .registrationClientUrl(registrationClientUri);
+
+ ClientSettings clientSettings = registeredClient.getClientSettings();
+
+ if (clientSettings.getJwkSetUrl() != null) {
+ builder.jwkSetUrl(clientSettings.getJwkSetUrl());
+ }
+
+ if (clientSettings.getTokenEndpointAuthenticationSigningAlgorithm() != null) {
+ builder.tokenEndpointAuthenticationSigningAlgorithm(clientSettings.getTokenEndpointAuthenticationSigningAlgorithm().getName());
+ }
+
+ return builder.build();
+ // @formatter:on
+ }
+
+}
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java
index 4c8458b4..af408b70 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java
@@ -25,10 +25,8 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
-import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
@@ -36,8 +34,12 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
+import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
+import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter;
+import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
@@ -47,12 +49,15 @@ import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
/**
- * A {@code Filter} that processes OpenID Connect Dynamic Client Registration (and Configuration) 1.0 Requests.
+ * A {@code Filter} that processes OpenID Connect 1.0 Dynamic Client Registration (and Client Read) Requests.
*
* @author Ovidiu Popa
* @author Joe Grandja
* @since 0.1.1
* @see OidcClientRegistration
+ * @see OidcClientRegistrationAuthenticationConverter
+ * @see OidcClientRegistrationAuthenticationProvider
+ * @see OidcClientConfigurationAuthenticationProvider
* @see 3. Client Registration Endpoint
* @see 4. Client Configuration Endpoint
*/
@@ -68,6 +73,7 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
new OidcClientRegistrationHttpMessageConverter();
private final HttpMessageConverter errorHttpResponseConverter =
new OAuth2ErrorHttpMessageConverter();
+ private AuthenticationConverter authenticationConverter;
/**
* Constructs an {@code OidcClientRegistrationEndpointFilter} using the provided parameters.
@@ -92,11 +98,12 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
this.clientRegistrationEndpointMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
clientRegistrationEndpointUri, HttpMethod.POST.name()),
- createConfigureClientMatcher(clientRegistrationEndpointUri));
+ createClientConfigurationMatcher(clientRegistrationEndpointUri));
+ this.authenticationConverter = new OidcClientRegistrationAuthenticationConverter();
}
- private static RequestMatcher createConfigureClientMatcher(String clientRegistrationEndpointUri) {
- RequestMatcher configureClientGetMatcher = new AntPathRequestMatcher(
+ private static RequestMatcher createClientConfigurationMatcher(String clientRegistrationEndpointUri) {
+ RequestMatcher clientConfigurationGetMatcher = new AntPathRequestMatcher(
clientRegistrationEndpointUri, HttpMethod.GET.name());
RequestMatcher clientIdMatcher = request -> {
@@ -104,7 +111,7 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
return StringUtils.hasText(clientId);
};
- return new AndRequestMatcher(configureClientGetMatcher, clientIdMatcher);
+ return new AndRequestMatcher(clientConfigurationGetMatcher, clientIdMatcher);
}
@Override
@@ -117,7 +124,8 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
}
try {
- OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = convert(request);
+ OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication =
+ (OidcClientRegistrationAuthenticationToken) this.authenticationConverter.convert(request);
OidcClientRegistrationAuthenticationToken clientRegistrationAuthenticationResult =
(OidcClientRegistrationAuthenticationToken) this.authenticationManager.authenticate(clientRegistrationAuthentication);
@@ -142,25 +150,6 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
}
}
- private OidcClientRegistrationAuthenticationToken convert(HttpServletRequest request) throws Exception {
- Authentication principal = SecurityContextHolder.getContext().getAuthentication();
-
- if ("POST".equals(request.getMethod())) {
- OidcClientRegistration clientRegistration = this.clientRegistrationHttpMessageConverter.read(
- OidcClientRegistration.class, new ServletServerHttpRequest(request));
- return new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
- }
-
- // client_id (REQUIRED)
- String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
- String[] clientIdParameters = request.getParameterValues(OAuth2ParameterNames.CLIENT_ID);
- if (!StringUtils.hasText(clientId) || clientIdParameters.length != 1) {
- throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
- }
-
- return new OidcClientRegistrationAuthenticationToken(principal, clientId);
- }
-
private void sendClientRegistrationResponse(HttpServletResponse response, HttpStatus httpStatus, OidcClientRegistration clientRegistration) throws IOException {
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
httpResponse.setStatusCode(httpStatus);
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java
new file mode 100644
index 00000000..0bcebec0
--- /dev/null
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2020-2022 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.oauth2.server.authorization.oidc.web.authentication;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
+import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.util.StringUtils;
+
+/**
+ * Attempts to extract an OpenID Connect 1.0 Dynamic Client Registration (or Client Read) Request from {@link HttpServletRequest}
+ * and then converts to an {@link OidcClientRegistrationAuthenticationToken} used for authenticating the request.
+ *
+ * @author Joe Grandja
+ * @since 0.4.0
+ * @see AuthenticationConverter
+ * @see OidcClientRegistrationAuthenticationToken
+ * @see OidcClientRegistrationEndpointFilter
+ */
+public final class OidcClientRegistrationAuthenticationConverter implements AuthenticationConverter {
+ private final HttpMessageConverter clientRegistrationHttpMessageConverter =
+ new OidcClientRegistrationHttpMessageConverter();
+
+ @Override
+ public Authentication convert(HttpServletRequest request) {
+ Authentication principal = SecurityContextHolder.getContext().getAuthentication();
+
+ if ("POST".equals(request.getMethod())) {
+ OidcClientRegistration clientRegistration;
+ try {
+ clientRegistration = this.clientRegistrationHttpMessageConverter.read(
+ OidcClientRegistration.class, new ServletServerHttpRequest(request));
+ } catch (Exception ex) {
+ OAuth2Error error = new OAuth2Error(
+ OAuth2ErrorCodes.INVALID_REQUEST,
+ "OpenID Client Registration Error: " + ex.getMessage(),
+ "https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError");
+ throw new OAuth2AuthenticationException(error, ex);
+ }
+ return new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
+ }
+
+ // client_id (REQUIRED)
+ String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
+ if (!StringUtils.hasText(clientId) ||
+ request.getParameterValues(OAuth2ParameterNames.CLIENT_ID).length != 1) {
+ throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
+ }
+
+ return new OidcClientRegistrationAuthenticationToken(principal, clientId);
+ }
+
+}
diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java
new file mode 100644
index 00000000..0aa44586
--- /dev/null
+++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2020-2022 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.oauth2.server.authorization.oidc.authentication;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.oauth2.jwt.JwsHeader;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtClaimsSet;
+import org.springframework.security.oauth2.jwt.TestJwsHeaders;
+import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
+import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
+import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
+import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link OidcClientConfigurationAuthenticationProvider}.
+ *
+ * @author Ovidiu Popa
+ * @author Joe Grandja
+ */
+public class OidcClientConfigurationAuthenticationProviderTests {
+ private RegisteredClientRepository registeredClientRepository;
+ private OAuth2AuthorizationService authorizationService;
+ private AuthorizationServerSettings authorizationServerSettings;
+ private OidcClientConfigurationAuthenticationProvider authenticationProvider;
+
+ @Before
+ public void setUp() {
+ this.registeredClientRepository = mock(RegisteredClientRepository.class);
+ this.authorizationService = mock(OAuth2AuthorizationService.class);
+ this.authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build();
+ AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null));
+ this.authenticationProvider = new OidcClientConfigurationAuthenticationProvider(
+ this.registeredClientRepository, this.authorizationService);
+ }
+
+ @After
+ public void cleanup() {
+ AuthorizationServerContextHolder.resetContext();
+ }
+
+ @Test
+ public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new OidcClientConfigurationAuthenticationProvider(null, this.authorizationService))
+ .withMessage("registeredClientRepository cannot be null");
+ }
+
+ @Test
+ public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new OidcClientConfigurationAuthenticationProvider(this.registeredClientRepository, null))
+ .withMessage("authorizationService cannot be null");
+ }
+
+ @Test
+ public void supportsWhenTypeOidcClientRegistrationAuthenticationTokenThenReturnTrue() {
+ assertThat(this.authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).isTrue();
+ }
+
+ @Test
+ public void authenticateWhenPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() {
+ TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
+
+ OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+ principal, "client-id");
+
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+ .isInstanceOf(OAuth2AuthenticationException.class)
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+ .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
+ }
+
+ @Test
+ public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
+ JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwtClientConfiguration());
+
+ OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+ principal, "client-id");
+
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+ .isInstanceOf(OAuth2AuthenticationException.class)
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+ .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
+ }
+
+ @Test
+ public void authenticateWhenAccessTokenNotFoundThenThrowOAuth2AuthenticationException() {
+ Jwt jwt = createJwtClientConfiguration();
+ JwtAuthenticationToken principal = new JwtAuthenticationToken(
+ jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
+
+ OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+ principal, "client-id");
+
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+ .isInstanceOf(OAuth2AuthenticationException.class)
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+ .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
+ verify(this.authorizationService).findByToken(
+ eq(jwt.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+ }
+
+ @Test
+ public void authenticateWhenAccessTokenNotActiveThenThrowOAuth2AuthenticationException() {
+ Jwt jwt = createJwtClientConfiguration();
+ OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+ jwt.getTokenValue(), jwt.getIssuedAt(),
+ jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+ OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+ registeredClient, jwtAccessToken, jwt.getClaims()).build();
+ authorization = OidcAuthenticationProviderUtils.invalidate(authorization, jwtAccessToken);
+ when(this.authorizationService.findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+ .thenReturn(authorization);
+
+ JwtAuthenticationToken principal = new JwtAuthenticationToken(
+ jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
+
+ OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+ principal, registeredClient.getClientId());
+
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+ .isInstanceOf(OAuth2AuthenticationException.class)
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+ .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
+ verify(this.authorizationService).findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+ }
+
+ @Test
+ public void authenticateWhenAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
+ Jwt jwt = createJwt(Collections.singleton("unauthorized.scope"));
+ OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+ jwt.getTokenValue(), jwt.getIssuedAt(),
+ jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+ OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+ registeredClient, jwtAccessToken, jwt.getClaims()).build();
+ when(this.authorizationService.findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+ .thenReturn(authorization);
+
+ JwtAuthenticationToken principal = new JwtAuthenticationToken(
+ jwt, AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope"));
+
+ OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+ principal, registeredClient.getClientId());
+
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+ .isInstanceOf(OAuth2AuthenticationException.class)
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+ .isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
+ verify(this.authorizationService).findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+ }
+
+ @Test
+ public void authenticateWhenAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() {
+ Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.read", "scope1")));
+ OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+ jwt.getTokenValue(), jwt.getIssuedAt(),
+ jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+ OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+ registeredClient, jwtAccessToken, jwt.getClaims()).build();
+ when(this.authorizationService.findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+ .thenReturn(authorization);
+
+ JwtAuthenticationToken principal = new JwtAuthenticationToken(
+ jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read", "SCOPE_scope1"));
+
+ OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+ principal, registeredClient.getClientId());
+
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+ .isInstanceOf(OAuth2AuthenticationException.class)
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+ .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
+ verify(this.authorizationService).findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+ }
+
+ @Test
+ public void authenticateWhenRegisteredClientNotFoundThenThrowOAuth2AuthenticationException() {
+ Jwt jwt = createJwtClientConfiguration();
+ OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+ jwt.getTokenValue(), jwt.getIssuedAt(),
+ jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+ OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+ registeredClient, jwtAccessToken, jwt.getClaims()).build();
+ when(this.authorizationService.findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+ .thenReturn(authorization);
+
+ JwtAuthenticationToken principal = new JwtAuthenticationToken(
+ jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
+
+ OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+ principal, registeredClient.getClientId());
+
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+ .isInstanceOf(OAuth2AuthenticationException.class)
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+ .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
+ verify(this.authorizationService).findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+ verify(this.registeredClientRepository).findByClientId(
+ eq(registeredClient.getClientId()));
+ }
+
+ @Test
+ public void authenticateWhenClientIdNotEqualToAuthorizedClientThenThrowOAuth2AuthenticationException() {
+ Jwt jwt = createJwtClientConfiguration();
+ OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+ jwt.getTokenValue(), jwt.getIssuedAt(),
+ jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+ RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient()
+ .id("registration-2").clientId("client-2").build();
+ OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+ authorizedRegisteredClient, jwtAccessToken, jwt.getClaims()).build();
+ when(this.authorizationService.findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+ .thenReturn(authorization);
+ when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
+ .thenReturn(registeredClient);
+
+ JwtAuthenticationToken principal = new JwtAuthenticationToken(
+ jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
+
+ OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+ principal, registeredClient.getClientId());
+
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+ .isInstanceOf(OAuth2AuthenticationException.class)
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
+ .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
+ verify(this.authorizationService).findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+ verify(this.registeredClientRepository).findByClientId(
+ eq(registeredClient.getClientId()));
+ }
+
+ @Test
+ public void authenticateWhenValidAccessTokenThenReturnClientRegistration() {
+ Jwt jwt = createJwtClientConfiguration();
+ OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+ jwt.getTokenValue(), jwt.getIssuedAt(),
+ jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
+ .clientAuthenticationMethods((clientAuthenticationMethods) -> {
+ clientAuthenticationMethods.clear();
+ clientAuthenticationMethods.add(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
+ })
+ .clientSettings(
+ ClientSettings.builder()
+ .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS512)
+ .jwkSetUrl("https://client.example.com/jwks")
+ .build()
+ )
+ .build();
+ OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
+ registeredClient, jwtAccessToken, jwt.getClaims()).build();
+ when(this.authorizationService.findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
+ .thenReturn(authorization);
+ when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
+ .thenReturn(registeredClient);
+
+ JwtAuthenticationToken principal = new JwtAuthenticationToken(
+ jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
+
+ OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
+ principal, registeredClient.getClientId());
+
+ OidcClientRegistrationAuthenticationToken authenticationResult =
+ (OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
+
+ verify(this.authorizationService).findByToken(
+ eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
+ verify(this.registeredClientRepository).findByClientId(
+ eq(registeredClient.getClientId()));
+
+ // verify that the "registration" access token is not invalidated after it is used
+ verify(this.authorizationService, never()).save(eq(authorization));
+ assertThat(authorization.getAccessToken().isInvalidated()).isFalse();
+
+ OidcClientRegistration clientRegistrationResult = authenticationResult.getClientRegistration();
+ assertThat(clientRegistrationResult.getClientId()).isEqualTo(registeredClient.getClientId());
+ assertThat(clientRegistrationResult.getClientIdIssuedAt()).isEqualTo(registeredClient.getClientIdIssuedAt());
+ assertThat(clientRegistrationResult.getClientSecret()).isEqualTo(registeredClient.getClientSecret());
+ assertThat(clientRegistrationResult.getClientSecretExpiresAt()).isEqualTo(registeredClient.getClientSecretExpiresAt());
+ assertThat(clientRegistrationResult.getClientName()).isEqualTo(registeredClient.getClientName());
+ assertThat(clientRegistrationResult.getRedirectUris())
+ .containsExactlyInAnyOrderElementsOf(registeredClient.getRedirectUris());
+
+ List grantTypes = new ArrayList<>();
+ registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
+ grantTypes.add(authorizationGrantType.getValue()));
+ assertThat(clientRegistrationResult.getGrantTypes()).containsExactlyInAnyOrderElementsOf(grantTypes);
+
+ assertThat(clientRegistrationResult.getResponseTypes())
+ .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
+ assertThat(clientRegistrationResult.getScopes())
+ .containsExactlyInAnyOrderElementsOf(registeredClient.getScopes());
+ assertThat(clientRegistrationResult.getTokenEndpointAuthenticationMethod())
+ .isEqualTo(registeredClient.getClientAuthenticationMethods().iterator().next().getValue());
+ assertThat(clientRegistrationResult.getTokenEndpointAuthenticationSigningAlgorithm())
+ .isEqualTo(registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm().getName());
+ assertThat(clientRegistrationResult.getJwkSetUrl().toString())
+ .isEqualTo(registeredClient.getClientSettings().getJwkSetUrl());
+ assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm())
+ .isEqualTo(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName());
+
+ AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
+ String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(authorizationServerContext.getIssuer())
+ .path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint())
+ .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()).toUriString();
+
+ assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()).isEqualTo(expectedRegistrationClientUrl);
+ assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNull();
+ }
+
+ private static Jwt createJwtClientConfiguration() {
+ return createJwt(Collections.singleton("client.read"));
+ }
+
+ private static Jwt createJwt(Set scopes) {
+ // @formatter:off
+ JwsHeader jwsHeader = TestJwsHeaders.jwsHeader()
+ .build();
+ JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet()
+ .claim(OAuth2ParameterNames.SCOPE, scopes)
+ .build();
+ Jwt jwt = Jwt.withTokenValue("jwt-access-token")
+ .headers(headers -> headers.putAll(jwsHeader.getHeaders()))
+ .claims(claims -> claims.putAll(jwtClaimsSet.getClaims()))
+ .build();
+ // @formatter:on
+ return jwt;
+ }
+
+}
diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java
index a7191385..e471bd8a 100644
--- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java
+++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java
@@ -58,7 +58,6 @@ import org.springframework.security.oauth2.server.authorization.context.TestAuth
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
-import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
@@ -72,7 +71,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -225,7 +223,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
- public void authenticateWhenClientRegistrationRequestAndAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
+ public void authenticateWhenAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwt(Collections.singleton("unauthorized.scope"));
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -255,7 +253,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
- public void authenticateWhenClientRegistrationRequestAndAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() {
+ public void authenticateWhenAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.create", "scope1")));
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -285,7 +283,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
- public void authenticateWhenClientRegistrationRequestAndInvalidRedirectUriThenThrowOAuth2AuthenticationException() {
+ public void authenticateWhenInvalidRedirectUriThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientRegistration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -320,7 +318,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
- public void authenticateWhenClientRegistrationRequestAndRedirectUriContainsFragmentThenThrowOAuth2AuthenticationException() {
+ public void authenticateWhenRedirectUriContainsFragmentThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientRegistration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -355,7 +353,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
- public void authenticateWhenClientRegistrationRequestAndInvalidTokenEndpointAuthenticationMethodThenThrowOAuth2AuthenticationException() {
+ public void authenticateWhenInvalidTokenEndpointAuthenticationMethodThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientRegistration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -434,7 +432,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
- public void authenticateWhenClientRegistrationRequestAndTokenEndpointAuthenticationSigningAlgorithmNotProvidedThenDefaults() {
+ public void authenticateWhenTokenEndpointAuthenticationSigningAlgorithmNotProvidedThenDefaults() {
Jwt jwt = createJwtClientRegistration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -521,7 +519,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
- public void authenticateWhenClientRegistrationRequestAndValidAccessTokenThenReturnClientRegistration() {
+ public void authenticateWhenValidAccessTokenThenReturnClientRegistration() {
Jwt jwt = createJwtClientRegistration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -622,202 +620,6 @@ public class OidcClientRegistrationAuthenticationProviderTests {
assertThat(clientRegistrationResult.getRegistrationAccessToken()).isEqualTo(jwt.getTokenValue());
}
- @Test
- public void authenticateWhenClientConfigurationRequestAndAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
- Jwt jwt = createJwt(Collections.singleton("unauthorized.scope"));
- OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
- jwt.getTokenValue(), jwt.getIssuedAt(),
- jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
- OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
- registeredClient, jwtAccessToken, jwt.getClaims()).build();
- when(this.authorizationService.findByToken(
- eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
- .thenReturn(authorization);
-
- JwtAuthenticationToken principal = new JwtAuthenticationToken(
- jwt, AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope"));
-
- OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
- principal, registeredClient.getClientId());
-
- assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
- .isInstanceOf(OAuth2AuthenticationException.class)
- .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
- .isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
- verify(this.authorizationService).findByToken(
- eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
- }
-
- @Test
- public void authenticateWhenClientConfigurationRequestAndAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() {
- Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.read", "scope1")));
- OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
- jwt.getTokenValue(), jwt.getIssuedAt(),
- jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
- OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
- registeredClient, jwtAccessToken, jwt.getClaims()).build();
- when(this.authorizationService.findByToken(
- eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
- .thenReturn(authorization);
-
- JwtAuthenticationToken principal = new JwtAuthenticationToken(
- jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read", "SCOPE_scope1"));
-
- OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
- principal, registeredClient.getClientId());
-
- assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
- .isInstanceOf(OAuth2AuthenticationException.class)
- .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
- .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
- verify(this.authorizationService).findByToken(
- eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
- }
-
- @Test
- public void authenticateWhenClientConfigurationRequestAndRegisteredClientNotFoundThenThrowOAuth2AuthenticationException() {
- Jwt jwt = createJwtClientConfiguration();
- OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
- jwt.getTokenValue(), jwt.getIssuedAt(),
- jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
- OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
- registeredClient, jwtAccessToken, jwt.getClaims()).build();
- when(this.authorizationService.findByToken(
- eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
- .thenReturn(authorization);
-
- JwtAuthenticationToken principal = new JwtAuthenticationToken(
- jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
-
- OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
- principal, registeredClient.getClientId());
-
- assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
- .isInstanceOf(OAuth2AuthenticationException.class)
- .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
- .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
- verify(this.authorizationService).findByToken(
- eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
- verify(this.registeredClientRepository).findByClientId(
- eq(registeredClient.getClientId()));
- }
-
- @Test
- public void authenticateWhenClientConfigurationRequestClientIdNotEqualToAuthorizedClientThenThrowOAuth2AuthenticationException() {
- Jwt jwt = createJwtClientConfiguration();
- OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
- jwt.getTokenValue(), jwt.getIssuedAt(),
- jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
- RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient()
- .id("registration-2").clientId("client-2").build();
- OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
- authorizedRegisteredClient, jwtAccessToken, jwt.getClaims()).build();
- when(this.authorizationService.findByToken(
- eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
- .thenReturn(authorization);
- when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
- .thenReturn(registeredClient);
-
- JwtAuthenticationToken principal = new JwtAuthenticationToken(
- jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
-
- OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
- principal, registeredClient.getClientId());
-
- assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
- .isInstanceOf(OAuth2AuthenticationException.class)
- .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
- .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
- verify(this.authorizationService).findByToken(
- eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
- verify(this.registeredClientRepository).findByClientId(
- eq(registeredClient.getClientId()));
- }
-
- @Test
- public void authenticateWhenClientConfigurationRequestAndValidAccessTokenThenReturnClientRegistration() {
- Jwt jwt = createJwtClientConfiguration();
- OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
- jwt.getTokenValue(), jwt.getIssuedAt(),
- jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
- .clientAuthenticationMethods((clientAuthenticationMethods) -> {
- clientAuthenticationMethods.clear();
- clientAuthenticationMethods.add(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
- })
- .clientSettings(
- ClientSettings.builder()
- .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS512)
- .jwkSetUrl("https://client.example.com/jwks")
- .build()
- )
- .build();
- OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
- registeredClient, jwtAccessToken, jwt.getClaims()).build();
- when(this.authorizationService.findByToken(
- eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
- .thenReturn(authorization);
- when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
- .thenReturn(registeredClient);
-
- JwtAuthenticationToken principal = new JwtAuthenticationToken(
- jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
-
- OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
- principal, registeredClient.getClientId());
-
- OidcClientRegistrationAuthenticationToken authenticationResult =
- (OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
-
- verify(this.authorizationService).findByToken(
- eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
- verify(this.registeredClientRepository).findByClientId(
- eq(registeredClient.getClientId()));
-
- // verify that the "registration" access token is not invalidated after it is used
- verify(this.authorizationService, never()).save(eq(authorization));
- assertThat(authorization.getAccessToken().isInvalidated()).isFalse();
-
- OidcClientRegistration clientRegistrationResult = authenticationResult.getClientRegistration();
- assertThat(clientRegistrationResult.getClientId()).isEqualTo(registeredClient.getClientId());
- assertThat(clientRegistrationResult.getClientIdIssuedAt()).isEqualTo(registeredClient.getClientIdIssuedAt());
- assertThat(clientRegistrationResult.getClientSecret()).isEqualTo(registeredClient.getClientSecret());
- assertThat(clientRegistrationResult.getClientSecretExpiresAt()).isEqualTo(registeredClient.getClientSecretExpiresAt());
- assertThat(clientRegistrationResult.getClientName()).isEqualTo(registeredClient.getClientName());
- assertThat(clientRegistrationResult.getRedirectUris())
- .containsExactlyInAnyOrderElementsOf(registeredClient.getRedirectUris());
-
- List grantTypes = new ArrayList<>();
- registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
- grantTypes.add(authorizationGrantType.getValue()));
- assertThat(clientRegistrationResult.getGrantTypes()).containsExactlyInAnyOrderElementsOf(grantTypes);
-
- assertThat(clientRegistrationResult.getResponseTypes())
- .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
- assertThat(clientRegistrationResult.getScopes())
- .containsExactlyInAnyOrderElementsOf(registeredClient.getScopes());
- assertThat(clientRegistrationResult.getTokenEndpointAuthenticationMethod())
- .isEqualTo(registeredClient.getClientAuthenticationMethods().iterator().next().getValue());
- assertThat(clientRegistrationResult.getTokenEndpointAuthenticationSigningAlgorithm())
- .isEqualTo(registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm().getName());
- assertThat(clientRegistrationResult.getJwkSetUrl().toString())
- .isEqualTo(registeredClient.getClientSettings().getJwkSetUrl());
- assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm())
- .isEqualTo(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName());
-
- AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
- String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(authorizationServerContext.getIssuer())
- .path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint())
- .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()).toUriString();
-
- assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()).isEqualTo(expectedRegistrationClientUrl);
- assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNull();
- }
-
private static Jwt createJwtClientRegistration() {
return createJwt(Collections.singleton("client.create"));
}