+
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ The *Client Authenticator* drop-down list specifies the type of credential to us

*Client ID and Secret*

This choice is the default setting. The secret is automatically generated. Click *Regenerate* to recreate the secret if necessary.
This choice is the default setting. A random secret is generated automatically, but you can override it with a specific value if needed. The client secret can also reference a value stored in an external vault. Click *Regenerate* to recreate random secret if necessary.


.Signed JWT
image:images/client-credentials-jwt.png[Signed JWT]
Expand Down
3 changes: 3 additions & 0 deletions docs/documentation/server_admin/topics/vault.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ In the <<_ldap,LDAP settings>> of LDAP-based user federation.
OIDC identity provider secret::
In the _Client Secret_ inside identity provider <<_identity_broker_oidc,OpenID Connect Config>>

OIDC client secret::
In the _Client Secret_ inside confidential <<_client-credentials,OpenID Connect Client>>.

[[_vault-key-resolvers]]
=== Key resolvers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2992,6 +2992,7 @@ clientAuthenticationHelp=The client authentication method (cfr. https\://openid.
kerberosRealmHelp=Name of kerberos realm. For example, FOO.ORG.
roleCreateError=Could not create role\: {{error}}
clientSecretHelp=The client secret registered with the identity provider. This field is able to obtain its value from vault, use ${vault.ID} format.
oidcClientSecretHelp=The client secret registered with the client. This field is able to obtain its value from vault, use ${vault.ID} format.
offlineSessionMax=Offline Session Max
generatedUserInfoHelp=See the example User Info, which will be provided by the User Info Endpoint.
dynamicScopeFormat=Dynamic scope format
Expand Down
16 changes: 13 additions & 3 deletions js/apps/admin-ui/src/clients/credentials/ClientSecret.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import {
SplitItem,
} from "@patternfly/react-core";
import { useEffect, useState } from "react";
import { useFormContext } from "react-hook-form";
import { useFormContext, Controller } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { PasswordInput } from "@keycloak/keycloak-ui-shared";
import { PasswordInput, HelpItem } from "@keycloak/keycloak-ui-shared";
import { useAdminClient } from "../../admin-client";
import { useAlerts } from "@keycloak/keycloak-ui-shared";
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
Expand Down Expand Up @@ -47,7 +47,11 @@ const SecretInput = ({
<SplitItem isFilled>
<InputGroup>
<InputGroupItem isFill>
<PasswordInput id={id} value={secret} readOnly />
<Controller
name="secret"
control={form.control}
render={({ field }) => <PasswordInput id={id} {...field} />}
/>
</InputGroupItem>
<InputGroupItem>
<CopyToClipboardButton
Expand Down Expand Up @@ -134,6 +138,12 @@ export const ClientSecret = ({ client, secret, toggle }: ClientSecretProps) => {
label={t("clientSecret")}
fieldId="kc-client-secret"
className="pf-v5-u-my-md"
labelIcon={
<HelpItem
helpText={t("oidcClientSecretHelp")}
fieldLabelId="kc-client-secret"
/>
}
>
<SecretInput
id="kc-client-secret"
Expand Down
20 changes: 7 additions & 13 deletions js/apps/admin-ui/src/clients/credentials/Credentials.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
Card,
CardBody,
ClipboardCopy,
Divider,
Form,
FormGroup,
PageSection,
Expand Down Expand Up @@ -193,24 +192,19 @@ export const Credentials = ({ client, save, refresh }: CredentialsProps) => {
/>
</Form>
)}
{selectedProvider?.supportsSecret && (
<ClientSecret
client={client}
secret={secret}
toggle={toggleClientSecretConfirm}
/>
)}
<ActionGroup>
<Button variant="primary" type="submit" isDisabled={!isDirty}>
{t("save")}
</Button>
</ActionGroup>
</CardBody>
{selectedProvider?.supportsSecret && (
<>
<Divider />
<CardBody>
<ClientSecret
client={client}
secret={secret}
toggle={toggleClientSecretConfirm}
/>
</CardBody>
</>
)}
</Card>
<Card isFlat>
<CardBody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.keycloak.authentication;

import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ConfiguredPerClientProvider;
import org.keycloak.provider.ProviderFactory;

Expand Down Expand Up @@ -52,6 +53,16 @@ public interface ClientAuthenticatorFactory extends ProviderFactory<ClientAuthen
*/
Map<String, Object> getAdapterConfiguration(ClientModel client);

/**
* Get configuration, which needs to be used for adapter ( keycloak.json ) of particular client. Some implementations
* may return just template and user needs to edit the values according to his environment (For example fill the location of keystore file)
*
* @return
*/
default Map<String, Object> getAdapterConfiguration(KeycloakSession session, ClientModel client) {
return getAdapterConfiguration(client);
}

/**
* Get authentication methods for the specified protocol
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.keycloak.authentication.ClientAuthenticationFlowContext;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.OIDCClientSecretConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.provider.ProviderConfigProperty;
Expand All @@ -32,6 +33,7 @@
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
Expand Down Expand Up @@ -132,9 +134,9 @@ public void authenticateClient(ClientAuthenticationFlowContext context) {
return;
}

OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientModel(client);
OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientModel(client, context.getSession());

if (!client.validateSecret(clientSecret)) {
if (!wrapper.validatePrimarySecret(clientSecret)) {
if (!wrapper.validateRotatedSecret(clientSecret)){
reportFailedAuth(context);
return;
Expand Down Expand Up @@ -181,12 +183,18 @@ public List<ProviderConfigProperty> getConfigPropertiesPerClient() {
}

@Override
public Map<String, Object> getAdapterConfiguration(ClientModel client) {
public Map<String, Object> getAdapterConfiguration(KeycloakSession session, ClientModel client) {
Map<String, Object> result = new HashMap<>();
result.put(CredentialRepresentation.SECRET, client.getSecret());
String secret = client.getSecret();
result.put(CredentialRepresentation.SECRET, session.vault().getStringSecret(secret).get().orElse(secret));
return result;
}

@Override
public Map<String, Object> getAdapterConfiguration(ClientModel client) {
throw new UnsupportedOperationException("Not implemented");
}

@Override
public String getId() {
return PROVIDER_ID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public boolean verifySignature(AbstractJWTClientValidator validator) {
}

//
OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientModel(client);
OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientModel(client, context.getSession());
if (wrapper.isClientSecretExpired()) {
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, null);
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSecretConstants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.delegate.ClientModelLazyDelegate;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.utils.StringUtil;
Expand All @@ -28,16 +29,19 @@
*/
public class OIDCClientSecretConfigWrapper extends AbstractClientConfigWrapper {

private OIDCClientSecretConfigWrapper(ClientModel client, ClientRepresentation clientRep) {
private final KeycloakSession session;

private OIDCClientSecretConfigWrapper(ClientModel client, ClientRepresentation clientRep, KeycloakSession session) {
super(client, clientRep);
this.session = session;
}

public static OIDCClientSecretConfigWrapper fromClientModel(ClientModel client) {
return new OIDCClientSecretConfigWrapper(client, null);
public static OIDCClientSecretConfigWrapper fromClientModel(ClientModel client, KeycloakSession session) {
return new OIDCClientSecretConfigWrapper(client, null, session);
}

public static OIDCClientSecretConfigWrapper fromClientRepresentation(ClientRepresentation clientRep) {
return new OIDCClientSecretConfigWrapper(null, clientRep);
public static OIDCClientSecretConfigWrapper fromClientRepresentation(ClientRepresentation clientRep, KeycloakSession session) {
return new OIDCClientSecretConfigWrapper(null, clientRep, session);
}

public String getSecret() {
Expand Down Expand Up @@ -92,7 +96,8 @@ public boolean hasRotatedSecret() {
}

public String getClientRotatedSecret() {
return getAttribute(CLIENT_ROTATED_SECRET);
String secret = getAttribute(CLIENT_ROTATED_SECRET);
return session == null ? getAttribute(CLIENT_ROTATED_SECRET) : session.vault().getStringSecret(secret).get().orElse(secret);
}

public void setClientRotatedSecret(String secret) {
Expand Down Expand Up @@ -170,6 +175,19 @@ public boolean isClientRotatedSecretExpired() {
return true;
}

public String getClientPrimarySecret() {
String secret = clientModel.getSecret();
return session == null ? secret : session.vault().getStringSecret(secret).get().orElse(secret);
}

public boolean validatePrimarySecret(String secret) {
if (isClientSecretExpired()) {
return false;
}

return MessageDigest.isEqual(secret.getBytes(), getClientPrimarySecret().getBytes());
}

//validates the rotated secret (value and expiration)
public boolean validateRotatedSecret(String secret) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public Response generateInstallation(KeycloakSession session, RealmModel realm,
public static Map<String, Object> getClientCredentialsAdapterConfig(KeycloakSession session, ClientModel client) {
String clientAuthenticator = client.getClientAuthenticatorType();
ClientAuthenticatorFactory authenticator = (ClientAuthenticatorFactory) session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, clientAuthenticator);
return authenticator.getAdapterConfiguration(client);
return authenticator.getAdapterConfiguration(session, client);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private boolean isClientWithSecret(ClientModel client) {
private void executeOnAuthRequest() {
ClientModel client = session.getContext().getClient();
OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientModel(
client);
client, session);

if (!wrapper.hasClientSecretExpirationTime()) {
//first login with policy
Expand All @@ -99,7 +99,7 @@ private void executeOnAuthRequest() {

private void executeOnClientCreateOrUpdate(ClientCRUDContext adminContext) {
OIDCClientSecretConfigWrapper clientConfigWrapper = OIDCClientSecretConfigWrapper.fromClientModel(
adminContext.getTargetClient());
adminContext.getTargetClient(), session);
logger.debugv("Executing policy {0} for client {1}-{2} with configuration [ expirationPeriod: {3}, rotatedExpirationPeriod: {4}, remainExpirationPeriod: {5} ]", getName(), clientConfigWrapper.getId(), clientConfigWrapper.getName(), configuration.getExpirationPeriod(), configuration.getRotatedExpirationPeriod(), configuration.getRemainExpirationPeriod());
if (adminContext instanceof ClientSecretRotationContext
|| clientConfigWrapper.isClientSecretExpired()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ public static OIDCClientRepresentation toExternalResponse(KeycloakSession sessio
if (clientAuth.supportsSecret()) {
response.setClientSecret(client.getSecret());
response.setClientSecretExpiresAt(
OIDCClientSecretConfigWrapper.fromClientRepresentation(client).getClientSecretExpirationTime());
OIDCClientSecretConfigWrapper.fromClientRepresentation(client, session).getClientSecretExpirationTime());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,9 @@ private boolean showClientCredentialsAdapterConfig(ClientModel client) {

private Map<String, Object> getClientCredentialsAdapterConfig(ClientModel client) {
String clientAuthenticator = client.getClientAuthenticatorType();
ClientAuthenticatorFactory authenticator = (ClientAuthenticatorFactory) realmManager.getSession().getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, clientAuthenticator);
return authenticator.getAdapterConfiguration(client);
KeycloakSession session = realmManager.getSession();
ClientAuthenticatorFactory authenticator = (ClientAuthenticatorFactory) session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, clientAuthenticator);
return authenticator.getAdapterConfiguration(session, client);
}

private boolean isInternalClient(String realmName, String clientId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public Response update(final ClientRepresentation rep) {

if (!(boolean) session.getAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED)){
logger.debugv("Removing the previous rotation info for client {0}{1}, if there is",client.getClientId(),client.getName());
OIDCClientSecretConfigWrapper.fromClientModel(client).removeClientSecretRotationInfo();
OIDCClientSecretConfigWrapper.fromClientModel(client, session).removeClientSecretRotationInfo();
}
session.removeAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED);

Expand Down Expand Up @@ -298,7 +298,7 @@ public CredentialRepresentation regenerateSecret() {

if (!(boolean) session.getAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED)){
logger.debugv("Removing the previous rotation info for client {0}{1}, if there is",client.getClientId(),client.getName());
OIDCClientSecretConfigWrapper.fromClientModel(client).removeClientSecretRotationInfo();
OIDCClientSecretConfigWrapper.fromClientModel(client, session).removeClientSecretRotationInfo();
}

adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(rep).success();
Expand Down Expand Up @@ -773,7 +773,7 @@ public Response invalidateRotatedSecret() {

logger.debug("delete rotated secret");

OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientModel(client);
OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientModel(client, session);

CredentialRepresentation rep = new CredentialRepresentation();
rep.setType(CredentialRepresentation.SECRET);
Expand Down Expand Up @@ -805,7 +805,7 @@ public CredentialRepresentation getClientRotatedSecret() {
auth.clients().requireView(client);

logger.debug("getClientRotatedSecret");
OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientModel(client);
OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientModel(client, session);
if (!wrapper.hasRotatedSecret())
throw new NotFoundException("Client does not have a rotated secret");
else {
Expand Down
Loading
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载