diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java b/server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java index 8da52f0d67..d8539f8f20 100644 --- a/server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java @@ -69,4 +69,12 @@ public interface ClientAuthenticatorFactory extends ProviderFactory getProtocolAuthenticatorMethods(String loginProtocol); + /** + * Is this authenticator supports client secret? + * + * @return if it supports secret + */ + default boolean supportsSecret() { + return false; + } } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java index 1ac1d37aaf..df4bb426b6 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java @@ -204,6 +204,11 @@ public class ClientIdAndSecretAuthenticator extends AbstractClientAuthenticator } } + @Override + public boolean supportsSecret() { + return true; + } + private void reportFailedAuth(ClientAuthenticationFlowContext context) { Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "unauthorized_client", "Invalid client secret"); context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java index 218726fdeb..f8ebd98b33 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java @@ -16,6 +16,28 @@ */ package org.keycloak.authentication.authenticators.client; +import org.jboss.logging.Logger; +import org.keycloak.OAuth2Constants; +import org.keycloak.authentication.AuthenticationFlowError; +import org.keycloak.authentication.ClientAuthenticationFlowContext; +import org.keycloak.common.util.Time; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.models.AuthenticationExecutionModel.Requirement; +import org.keycloak.models.ClientModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.SingleUseObjectProvider; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; +import org.keycloak.protocol.oidc.OIDCClientSecretConfigWrapper; +import org.keycloak.protocol.oidc.OIDCConfigAttributes; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.OIDCLoginProtocolService; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.representations.JsonWebToken; +import org.keycloak.services.ServicesLogger; +import org.keycloak.services.Urls; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -25,29 +47,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; - -import org.jboss.logging.Logger; -import org.keycloak.OAuth2Constants; -import org.keycloak.authentication.AuthenticationFlowError; -import org.keycloak.authentication.ClientAuthenticationFlowContext; -import org.keycloak.common.util.Time; -import org.keycloak.jose.jws.JWSInput; -import org.keycloak.models.AuthenticationExecutionModel.Requirement; -import org.keycloak.models.SingleUseObjectProvider; -import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; -import org.keycloak.protocol.oidc.OIDCClientSecretConfigWrapper; -import org.keycloak.protocol.oidc.OIDCLoginProtocol; -import org.keycloak.protocol.oidc.OIDCLoginProtocolService; -import org.keycloak.models.ClientModel; -import org.keycloak.models.RealmModel; -import org.keycloak.protocol.oidc.OIDCConfigAttributes; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.representations.JsonWebToken; -import org.keycloak.services.ServicesLogger; -import org.keycloak.services.Urls; - /** * Client authentication based on JWT signed by client secret instead of private key . * See specs for more details. @@ -254,6 +253,11 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator { } } + @Override + public boolean supportsSecret() { + return true; + } + @Override public String getId() { return PROVIDER_ID; diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java index 19529bb752..a756bd03c3 100755 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java @@ -17,15 +17,14 @@ package org.keycloak.services.clientregistration.oidc; +import com.google.common.collect.Streams; import org.keycloak.OAuth2Constants; import org.keycloak.authentication.ClientAuthenticator; import org.keycloak.authentication.ClientAuthenticatorFactory; -import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWKParser; -import org.keycloak.jose.jws.Algorithm; import org.keycloak.models.CibaConfig; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; @@ -51,8 +50,6 @@ import org.keycloak.util.JWKSUtils; import org.keycloak.util.JsonSerialization; import org.keycloak.utils.StringUtil; -import com.google.common.collect.Streams; - import java.io.IOException; import java.net.URI; import java.security.PublicKey; @@ -67,8 +64,8 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.keycloak.models.OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED; import static org.keycloak.models.CibaConfig.OIDC_CIBA_GRANT_ENABLED; +import static org.keycloak.models.OAuth2DeviceConfig.OAUTH2_DEVICE_AUTHORIZATION_GRANT_ENABLED; /** * @author Stian Thorgersen @@ -245,7 +242,7 @@ public class DescriptionConverter { configWrapper.setFrontChannelLogoutSessionRequired(false); } else { configWrapper.setFrontChannelLogoutSessionRequired(clientOIDC.getFrontchannelLogoutSessionRequired()); - } + } if (clientOIDC.getDefaultAcrValues() != null) { configWrapper.setAttributeMultivalued(Constants.DEFAULT_ACR_VALUES, clientOIDC.getDefaultAcrValues()); @@ -324,12 +321,12 @@ public class DescriptionConverter { if (oidcClientAuthMethods != null && !oidcClientAuthMethods.isEmpty()) { response.setTokenEndpointAuthMethod(oidcClientAuthMethods.iterator().next()); } - } - if (client.getClientAuthenticatorType().equals(ClientIdAndSecretAuthenticator.PROVIDER_ID)) { - response.setClientSecret(client.getSecret()); - response.setClientSecretExpiresAt( + if (clientAuth.supportsSecret()) { + response.setClientSecret(client.getSecret()); + response.setClientSecretExpiresAt( OIDCClientSecretConfigWrapper.fromClientRepresentation(client).getClientSecretExpirationTime()); + } } response.setClientName(client.getName()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java index 4621700ef5..56a7b432ad 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -284,6 +284,30 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { Assert.assertNull(kcClientRep.getSecret()); } + @Test + public void testClientSecretsWithAuthMethod() throws ClientRegistrationException { + OIDCClientRepresentation clientRep = createRep(); + clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS)); + clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.CLIENT_SECRET_JWT); + + OIDCClientRepresentation response = reg.oidc().create(clientRep); + Assert.assertEquals("client_secret_jwt", response.getTokenEndpointAuthMethod()); + Assert.assertNotNull(response.getClientSecret()); + Assert.assertNotNull(response.getClientSecretExpiresAt()); + + ClientRepresentation kcClientRep = getKeycloakClient(response.getClientId()); + Assert.assertFalse(kcClientRep.isPublicClient()); + Assert.assertNotNull(kcClientRep.getSecret()); + + // Updating + reg.auth(Auth.token(response)); + response.setTokenEndpointAuthMethod(OIDCLoginProtocol.TLS_CLIENT_AUTH); + OIDCClientRepresentation updated = reg.oidc().update(response); + Assert.assertEquals("tls_client_auth", updated.getTokenEndpointAuthMethod()); + Assert.assertNull(updated.getClientSecret()); + Assert.assertNull(updated.getClientSecretExpiresAt()); + } + @Test public void createClientFrontchannelLogoutSettings() throws ClientRegistrationException { // When frontchannelLogutSessionRequired is not set, it should be false by default per OIDC Client registration specification