Add client_secret
to response when token_endpoint_auth_method
is not private_key_jwt
(#12609)
Closes #12565
This commit is contained in:
parent
4643fd09e3
commit
c3c8b9f0c8
5 changed files with 71 additions and 33 deletions
|
@ -69,4 +69,12 @@ public interface ClientAuthenticatorFactory extends ProviderFactory<ClientAuthen
|
|||
*/
|
||||
Set<String> getProtocolAuthenticatorMethods(String loginProtocol);
|
||||
|
||||
/**
|
||||
* Is this authenticator supports client secret?
|
||||
*
|
||||
* @return if it supports secret
|
||||
*/
|
||||
default boolean supportsSecret() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">specs</a> 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;
|
||||
|
|
|
@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue