Browse Source

Adds ability to inject custom metadata at client registration

Closes gh-1172
pull/1345/head
Dmitriy Dubson 2 years ago committed by Joe Grandja
parent
commit
3de6a7dfd1
  1. 18
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java
  2. 109
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java
  3. 126
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java
  4. 6
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java
  5. 188
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java
  6. 7
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java
  7. 7
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java

18
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 the original author or authors.
* Copyright 2020-2023 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.
@ -36,6 +36,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; @@ -36,6 +36,7 @@ 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.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter;
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -46,11 +47,13 @@ import org.springframework.util.StringUtils; @@ -46,11 +47,13 @@ import org.springframework.util.StringUtils;
* @author Ovidiu Popa
* @author Joe Grandja
* @author Rafal Lewczuk
* @author Dmitriy Dubson
* @since 0.4.0
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
* @see OidcClientRegistrationAuthenticationToken
* @see OidcClientRegistrationAuthenticationProvider
* @see RegisteredClientOidcClientRegistrationConverter
* @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint">4. Client Configuration Endpoint</a>
*/
public final class OidcClientConfigurationAuthenticationProvider implements AuthenticationProvider {
@ -58,7 +61,7 @@ public final class OidcClientConfigurationAuthenticationProvider implements Auth @@ -58,7 +61,7 @@ public final class OidcClientConfigurationAuthenticationProvider implements Auth
private final Log logger = LogFactory.getLog(getClass());
private final RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationService authorizationService;
private final Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
private Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
/**
* Constructs an {@code OidcClientConfigurationAuthenticationProvider} using the provided parameters.
@ -75,6 +78,17 @@ public final class OidcClientConfigurationAuthenticationProvider implements Auth @@ -75,6 +78,17 @@ public final class OidcClientConfigurationAuthenticationProvider implements Auth
this.clientRegistrationConverter = new RegisteredClientOidcClientRegistrationConverter();
}
/**
* Sets the {@link Converter} used for converting an {@link RegisteredClient} to a {@link OidcClientRegistration}.
*
* @param clientRegistrationConverter the {@link Converter} used for converting an {@link RegisteredClient} to a {@link OidcClientRegistration}
* @since 1.2.0
*/
public void setClientRegistrationConverter(Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter) {
Assert.notNull(clientRegistrationConverter, "clientRegistrationConverter cannot be null");
this.clientRegistrationConverter = clientRegistrationConverter;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication =

109
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java

@ -17,15 +17,12 @@ package org.springframework.security.oauth2.server.authorization.oidc.authentica @@ -17,15 +17,12 @@ package org.springframework.security.oauth2.server.authorization.oidc.authentica
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
import java.util.Base64;
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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -35,8 +32,6 @@ import org.springframework.security.authentication.AuthenticationProvider; @@ -35,8 +32,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClaimAccessor;
@ -46,7 +41,6 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -46,7 +41,6 @@ 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.OAuth2Token;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
@ -59,8 +53,8 @@ import org.springframework.security.oauth2.server.authorization.client.Registere @@ -59,8 +53,8 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
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;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter;
import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
@ -75,6 +69,7 @@ import org.springframework.util.StringUtils; @@ -75,6 +69,7 @@ import org.springframework.util.StringUtils;
* @author Ovidiu Popa
* @author Joe Grandja
* @author Rafal Lewczuk
* @author Dmitriy Dubson
* @since 0.1.1
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
@ -91,7 +86,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe @@ -91,7 +86,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
private final RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
private final Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
private Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
private Converter<OidcClientRegistration, RegisteredClient> registeredClientConverter;
private PasswordEncoder passwordEncoder;
@ -172,6 +167,17 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe @@ -172,6 +167,17 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
this.registeredClientConverter = registeredClientConverter;
}
/**
* Sets the {@link Converter} used for converting an {@link RegisteredClient} to a {@link OidcClientRegistration}.
*
* @param clientRegistrationConverter the {@link Converter} used for converting an {@link RegisteredClient} to a {@link OidcClientRegistration}
* @since 1.2.0
*/
public void setClientRegistrationConverter(Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter) {
Assert.notNull(clientRegistrationConverter, "clientRegistrationConverter cannot be null");
this.clientRegistrationConverter = clientRegistrationConverter;
}
/**
* Sets the {@link PasswordEncoder} used to encode the {@link RegisteredClient#getClientSecret() client secret}.
* If not set, the client secret will be encoded using {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}.
@ -368,89 +374,4 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe @@ -368,89 +374,4 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
throw new OAuth2AuthenticationException(error);
}
private static final class OidcClientRegistrationRegisteredClientConverter implements Converter<OidcClientRegistration, RegisteredClient> {
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.getPostLogoutRedirectUris())) {
builder.postLogoutRedirectUris(postLogoutRedirectUris ->
postLogoutRedirectUris.addAll(clientRegistration.getPostLogoutRedirectUris()));
}
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
}
}
}

126
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java

@ -0,0 +1,126 @@ @@ -0,0 +1,126 @@
/*
* Copyright 2020-2023 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.converter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.util.CollectionUtils;
import java.time.Instant;
import java.util.Base64;
import java.util.UUID;
/**
* @author Joe Grandja
* @author Dmitriy Dubson
* @since 1.2.0
*/
public final class OidcClientRegistrationRegisteredClientConverter implements Converter<OidcClientRegistration, RegisteredClient> {
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.getPostLogoutRedirectUris())) {
builder.postLogoutRedirectUris(postLogoutRedirectUris ->
postLogoutRedirectUris.addAll(clientRegistration.getPostLogoutRedirectUris()));
}
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
}
}

6
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/RegisteredClientOidcClientRegistrationConverter.java → oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.oidc.authentication;
package org.springframework.security.oauth2.server.authorization.oidc.converter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@ -29,9 +29,9 @@ import org.springframework.web.util.UriComponentsBuilder; @@ -29,9 +29,9 @@ import org.springframework.web.util.UriComponentsBuilder;
/**
* @author Joe Grandja
* @since 0.4.0
* @since 1.2.0
*/
final class RegisteredClientOidcClientRegistrationConverter implements Converter<RegisteredClient, OidcClientRegistration> {
public final class RegisteredClientOidcClientRegistrationConverter implements Converter<RegisteredClient, OidcClientRegistration> {
@Override
public OidcClientRegistration convert(RegisteredClient registeredClient) {

188
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java

@ -17,11 +17,13 @@ package org.springframework.security.oauth2.server.authorization.config.annotati @@ -17,11 +17,13 @@ package org.springframework.security.oauth2.server.authorization.config.annotati
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
@ -59,8 +61,6 @@ import org.springframework.security.config.Customizer; @@ -59,8 +61,6 @@ import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@ -71,7 +71,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp @@ -71,7 +71,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
@ -92,11 +91,12 @@ import org.springframework.security.oauth2.server.authorization.oidc.OidcClientR @@ -92,11 +91,12 @@ import org.springframework.security.oauth2.server.authorization.oidc.OidcClientR
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.converter.OidcClientRegistrationRegisteredClientConverter;
import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter;
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.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.test.SpringTestContext;
import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension;
import org.springframework.security.web.SecurityFilterChain;
@ -131,6 +131,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @@ -131,6 +131,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Ovidiu Popa
* @author Joe Grandja
* @author Dmitriy Dubson
*/
@ExtendWith(SpringTestContextExtension.class)
public class OidcClientRegistrationTests {
@ -420,6 +421,7 @@ public class OidcClientRegistrationTests { @@ -420,6 +421,7 @@ public class OidcClientRegistrationTests {
.scope("scope2")
.claim("custom-metadata-name-1", "value-1")
.claim("custom-metadata-name-2", "value-2")
.claim("non-registered-custom-metadata", "value-3")
.build();
// @formatter:on
@ -428,10 +430,15 @@ public class OidcClientRegistrationTests { @@ -428,10 +430,15 @@ public class OidcClientRegistrationTests {
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
clientRegistrationResponse.getClientId());
assertThat(clientRegistrationResponse.<String>getClaim("custom-metadata-name-1")).isEqualTo("value-1");
assertThat(clientRegistrationResponse.<String>getClaim("custom-metadata-name-2")).isEqualTo("value-2");
assertThat(clientRegistrationResponse.<String>getClaim("non-registered-custom-metadata")).isNull();
assertThat(registeredClient.getClientSettings().<String>getSetting("custom-metadata-name-1"))
.isEqualTo("value-1");
assertThat(registeredClient.getClientSettings().<String>getSetting("custom-metadata-name-2"))
.isEqualTo("value-2");
assertThat(registeredClient.getClientSettings().<String>getSetting("non-registered-custom-metadata")).isNull();
}
private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
@ -581,7 +588,7 @@ public class OidcClientRegistrationTests { @@ -581,7 +588,7 @@ public class OidcClientRegistrationTests {
oidc
.clientRegistrationEndpoint(clientRegistration ->
clientRegistration
.authenticationProviders(configureRegisteredClientConverter())
.authenticationProviders(configureRegisteredClientConverters())
)
);
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
@ -600,111 +607,18 @@ public class OidcClientRegistrationTests { @@ -600,111 +607,18 @@ public class OidcClientRegistrationTests {
}
// @formatter:on
private Consumer<List<AuthenticationProvider>> configureRegisteredClientConverter() {
return (authenticationProviders) -> {
authenticationProviders.forEach((authenticationProvider) -> {
if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider) {
((OidcClientRegistrationAuthenticationProvider) authenticationProvider)
.setRegisteredClientConverter(new OidcClientRegistrationRegisteredClientConverter());
}
});
};
}
// NOTE:
// This is a copy of OidcClientRegistrationAuthenticationProvider.OidcClientRegistrationRegisteredClientConverter
// with a minor enhancement supporting custom metadata claims.
private static final class OidcClientRegistrationRegisteredClientConverter implements Converter<OidcClientRegistration, RegisteredClient> {
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.getPostLogoutRedirectUris())) {
builder.postLogoutRedirectUris(postLogoutRedirectUris ->
postLogoutRedirectUris.addAll(clientRegistration.getPostLogoutRedirectUris()));
}
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());
}
// Add custom metadata claims
clientRegistration.getClaims().forEach((claim, value) -> {
if (claim.startsWith("custom-metadata")) {
clientSettingsBuilder.setting(claim, value);
}
});
builder
.clientSettings(clientSettingsBuilder.build())
.tokenSettings(TokenSettings.builder()
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
.build());
return builder.build();
// @formatter:on
}
private Consumer<List<AuthenticationProvider>> configureRegisteredClientConverters() {
// @formatter:off
return (authenticationProviders) ->
authenticationProviders.forEach(authenticationProvider -> {
List<String> customClientMetadata = List.of("custom-metadata-name-1", "custom-metadata-name-2");
if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) {
provider.setRegisteredClientConverter(new CustomRegisteredClientConverter(customClientMetadata));
provider.setClientRegistrationConverter(new CustomClientRegistrationConverter(customClientMetadata));
}
});
// @formatter:on
}
}
@ -781,4 +695,54 @@ public class OidcClientRegistrationTests { @@ -781,4 +695,54 @@ public class OidcClientRegistrationTests {
}
static class CustomClientRegistrationConverter implements Converter<RegisteredClient, OidcClientRegistration> {
private final List<String> customMetadata;
private final RegisteredClientOidcClientRegistrationConverter delegate;
CustomClientRegistrationConverter(List<String> customMetadata) {
this.customMetadata = customMetadata;
this.delegate = new RegisteredClientOidcClientRegistrationConverter();
}
public OidcClientRegistration convert(RegisteredClient registeredClient) {
var clientRegistration = delegate.convert(registeredClient);
Map<String, Object> claims = new HashMap<>(clientRegistration.getClaims());
if (!CollectionUtils.isEmpty(customMetadata)) {
ClientSettings clientSettings = registeredClient.getClientSettings();
claims.putAll(customMetadata.stream()
.filter(metadatum -> clientSettings.getSetting(metadatum) != null)
.collect(Collectors.toMap(Function.identity(), clientSettings::getSetting)));
}
return OidcClientRegistration.withClaims(claims).build();
}
}
static class CustomRegisteredClientConverter implements Converter<OidcClientRegistration, RegisteredClient> {
private final List<String> customMetadata;
private final OidcClientRegistrationRegisteredClientConverter delegate;
CustomRegisteredClientConverter(List<String> customMetadata) {
this.customMetadata = customMetadata;
this.delegate = new OidcClientRegistrationRegisteredClientConverter();
}
public RegisteredClient convert(OidcClientRegistration clientRegistration) {
RegisteredClient convertedClient = delegate.convert(clientRegistration);
ClientSettings.Builder clientSettingsBuilder = ClientSettings
.withSettings(convertedClient.getClientSettings().getSettings());
if (!CollectionUtils.isEmpty(this.customMetadata)) {
clientRegistration.getClaims().forEach((claim, value) -> {
if (this.customMetadata.contains(claim)) {
clientSettingsBuilder.setting(claim, value);
}
});
}
return RegisteredClient.from(convertedClient).clientSettings(clientSettingsBuilder.build()).build();
}
}
}

7
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java

@ -378,6 +378,13 @@ public class OidcClientConfigurationAuthenticationProviderTests { @@ -378,6 +378,13 @@ public class OidcClientConfigurationAuthenticationProviderTests {
assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNull();
}
@Test
public void setClientRegistrationConverterWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.authenticationProvider.setClientRegistrationConverter(null))
.withMessage("clientRegistrationConverter cannot be null");
}
private static Jwt createJwtClientConfiguration() {
return createJwt(Collections.singleton("client.read"));
}

7
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java

@ -158,6 +158,13 @@ public class OidcClientRegistrationAuthenticationProviderTests { @@ -158,6 +158,13 @@ public class OidcClientRegistrationAuthenticationProviderTests {
.withMessage("registeredClientConverter cannot be null");
}
@Test
public void setClientRegistrationConverterWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.authenticationProvider.setClientRegistrationConverter(null))
.withMessage("clientRegistrationConverter cannot be null");
}
@Test
public void setPasswordEncoderWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.authenticationProvider.setPasswordEncoder(null))

Loading…
Cancel
Save