KEYCLOAK-18341 Support JWKS OAuth2 Client Metadata in the "by value" key loading method
This commit is contained in:
parent
3c19fae88b
commit
b31b60fffe
13 changed files with 245 additions and 82 deletions
|
@ -37,6 +37,7 @@ import org.keycloak.representations.idm.CertificateRepresentation;
|
||||||
import org.keycloak.services.util.CertificateInfoHelper;
|
import org.keycloak.services.util.CertificateInfoHelper;
|
||||||
import org.keycloak.services.util.ResolveRelative;
|
import org.keycloak.services.util.ResolveRelative;
|
||||||
import org.keycloak.util.JWKSUtils;
|
import org.keycloak.util.JWKSUtils;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
@ -74,6 +75,9 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
|
||||||
jwksUrl = ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), jwksUrl);
|
jwksUrl = ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), jwksUrl);
|
||||||
JSONWebKeySet jwks = JWKSHttpUtils.sendJwksRequest(session, jwksUrl);
|
JSONWebKeySet jwks = JWKSHttpUtils.sendJwksRequest(session, jwksUrl);
|
||||||
return JWKSUtils.getKeyWrappersForUse(jwks, keyUse);
|
return JWKSUtils.getKeyWrappersForUse(jwks, keyUse);
|
||||||
|
} else if (config.isUseJwksString()) {
|
||||||
|
JSONWebKeySet jwks = JsonSerialization.readValue(config.getJwksString(), JSONWebKeySet.class);
|
||||||
|
return JWKSUtils.getKeyWrappersForUse(jwks, keyUse);
|
||||||
} else if (keyUse == JWK.Use.SIG) {
|
} else if (keyUse == JWK.Use.SIG) {
|
||||||
try {
|
try {
|
||||||
CertificateRepresentation certInfo = CertificateInfoHelper.getCertificateFromClient(client, JWTClientAuthenticator.ATTR_PREFIX);
|
CertificateRepresentation certInfo = CertificateInfoHelper.getCertificateFromClient(client, JWTClientAuthenticator.ATTR_PREFIX);
|
||||||
|
|
|
@ -125,6 +125,24 @@ public class OIDCAdvancedConfigWrapper {
|
||||||
setAttribute(OIDCConfigAttributes.JWKS_URL, jwksUrl);
|
setAttribute(OIDCConfigAttributes.JWKS_URL, jwksUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isUseJwksString() {
|
||||||
|
String useJwksString = getAttribute(OIDCConfigAttributes.USE_JWKS_STRING);
|
||||||
|
return Boolean.parseBoolean(useJwksString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseJwksString(boolean useJwksString) {
|
||||||
|
String val = String.valueOf(useJwksString);
|
||||||
|
setAttribute(OIDCConfigAttributes.USE_JWKS_STRING, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJwksString() {
|
||||||
|
return getAttribute(OIDCConfigAttributes.JWKS_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJwksString(String jwksString) {
|
||||||
|
setAttribute(OIDCConfigAttributes.JWKS_STRING, jwksString);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isExcludeSessionStateFromAuthResponse() {
|
public boolean isExcludeSessionStateFromAuthResponse() {
|
||||||
String excludeSessionStateFromAuthResponse = getAttribute(OIDCConfigAttributes.EXCLUDE_SESSION_STATE_FROM_AUTH_RESPONSE);
|
String excludeSessionStateFromAuthResponse = getAttribute(OIDCConfigAttributes.EXCLUDE_SESSION_STATE_FROM_AUTH_RESPONSE);
|
||||||
return Boolean.parseBoolean(excludeSessionStateFromAuthResponse);
|
return Boolean.parseBoolean(excludeSessionStateFromAuthResponse);
|
||||||
|
|
|
@ -35,6 +35,10 @@ public final class OIDCConfigAttributes {
|
||||||
|
|
||||||
public static final String USE_JWKS_URL = "use.jwks.url";
|
public static final String USE_JWKS_URL = "use.jwks.url";
|
||||||
|
|
||||||
|
public static final String JWKS_STRING = "jwks.string";
|
||||||
|
|
||||||
|
public static final String USE_JWKS_STRING = "use.jwks.string";
|
||||||
|
|
||||||
public static final String EXCLUDE_SESSION_STATE_FROM_AUTH_RESPONSE = "exclude.session.state.from.auth.response";
|
public static final String EXCLUDE_SESSION_STATE_FROM_AUTH_RESPONSE = "exclude.session.state.from.auth.response";
|
||||||
|
|
||||||
public static final String USE_MTLS_HOK_TOKEN = "tls.client.certificate.bound.access.tokens";
|
public static final String USE_MTLS_HOK_TOKEN = "tls.client.certificate.bound.access.tokens";
|
||||||
|
|
|
@ -47,10 +47,12 @@ import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||||
import org.keycloak.services.clientregistration.ClientRegistrationException;
|
import org.keycloak.services.clientregistration.ClientRegistrationException;
|
||||||
import org.keycloak.services.util.CertificateInfoHelper;
|
import org.keycloak.services.util.CertificateInfoHelper;
|
||||||
import org.keycloak.util.JWKSUtils;
|
import org.keycloak.util.JWKSUtils;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.utils.StringUtil;
|
import org.keycloak.utils.StringUtil;
|
||||||
|
|
||||||
import com.google.common.collect.Streams;
|
import com.google.common.collect.Streams;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -237,40 +239,45 @@ public class DescriptionConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean setPublicKey(OIDCClientRepresentation clientOIDC, ClientRepresentation clientRep) {
|
private static boolean setPublicKey(OIDCClientRepresentation clientOIDC, ClientRepresentation clientRep) {
|
||||||
if (clientOIDC.getJwksUri() == null && clientOIDC.getJwks() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientOIDC.getJwksUri() != null && clientOIDC.getJwks() != null) {
|
|
||||||
throw new ClientRegistrationException("Illegal to use both jwks_uri and jwks");
|
|
||||||
}
|
|
||||||
|
|
||||||
OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||||
|
|
||||||
if (clientOIDC.getJwks() != null) {
|
if (clientOIDC.getJwks() != null) {
|
||||||
|
if (clientOIDC.getJwksUri() != null) {
|
||||||
|
throw new ClientRegistrationException("Illegal to use both jwks_uri and jwks");
|
||||||
|
}
|
||||||
|
|
||||||
JSONWebKeySet keySet = clientOIDC.getJwks();
|
JSONWebKeySet keySet = clientOIDC.getJwks();
|
||||||
JWK publicKeyJWk = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG);
|
JWK publicKeyJWk = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG);
|
||||||
|
|
||||||
|
try {
|
||||||
|
configWrapper.setJwksString(JsonSerialization.writeValueAsPrettyString(clientOIDC.getJwks()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Illegal jwks format");
|
||||||
|
}
|
||||||
|
configWrapper.setUseJwksString(true);
|
||||||
|
configWrapper.setUseJwksUrl(false);
|
||||||
|
|
||||||
if (publicKeyJWk == null) {
|
if (publicKeyJWk == null) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
PublicKey publicKey = JWKParser.create(publicKeyJWk).toPublicKey();
|
|
||||||
String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
|
|
||||||
CertificateRepresentation rep = new CertificateRepresentation();
|
|
||||||
rep.setPublicKey(publicKeyPem);
|
|
||||||
rep.setKid(publicKeyJWk.getKeyId());
|
|
||||||
CertificateInfoHelper.updateClientRepresentationCertificateInfo(clientRep, rep, JWTClientAuthenticator.ATTR_PREFIX);
|
|
||||||
|
|
||||||
configWrapper.setUseJwksUrl(false);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
} else {
|
PublicKey publicKey = JWKParser.create(publicKeyJWk).toPublicKey();
|
||||||
|
String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
|
||||||
|
CertificateRepresentation rep = new CertificateRepresentation();
|
||||||
|
rep.setPublicKey(publicKeyPem);
|
||||||
|
rep.setKid(publicKeyJWk.getKeyId());
|
||||||
|
CertificateInfoHelper.updateClientRepresentationCertificateInfo(clientRep, rep, JWTClientAuthenticator.ATTR_PREFIX);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (clientOIDC.getJwksUri() != null) {
|
||||||
configWrapper.setUseJwksUrl(true);
|
configWrapper.setUseJwksUrl(true);
|
||||||
configWrapper.setJwksUrl(clientOIDC.getJwksUri());
|
configWrapper.setJwksUrl(clientOIDC.getJwksUri());
|
||||||
|
configWrapper.setUseJwksString(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static OIDCClientRepresentation toExternalResponse(KeycloakSession session, ClientRepresentation client, URI uri) {
|
public static OIDCClientRepresentation toExternalResponse(KeycloakSession session, ClientRepresentation client, URI uri) {
|
||||||
OIDCClientRepresentation response = new OIDCClientRepresentation();
|
OIDCClientRepresentation response = new OIDCClientRepresentation();
|
||||||
|
@ -318,6 +325,13 @@ public class DescriptionConverter {
|
||||||
if (config.isUseJwksUrl()) {
|
if (config.isUseJwksUrl()) {
|
||||||
response.setJwksUri(config.getJwksUrl());
|
response.setJwksUri(config.getJwksUrl());
|
||||||
}
|
}
|
||||||
|
if (config.isUseJwksString()) {
|
||||||
|
try {
|
||||||
|
response.setJwks(JsonSerialization.readValue(config.getJwksString(), JSONWebKeySet.class));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Illegal jwks format");
|
||||||
|
}
|
||||||
|
}
|
||||||
// KEYCLOAK-6771 Certificate Bound Token
|
// KEYCLOAK-6771 Certificate Bound Token
|
||||||
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.5
|
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.5
|
||||||
if (config.isUseMtlsHokToken()) {
|
if (config.isUseMtlsHokToken()) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.protocol.ProtocolMapperConfigException;
|
import org.keycloak.protocol.ProtocolMapperConfigException;
|
||||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||||
import org.keycloak.protocol.oidc.grants.ciba.CibaClientValidation;
|
import org.keycloak.protocol.oidc.grants.ciba.CibaClientValidation;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper;
|
import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper;
|
||||||
import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils;
|
import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils;
|
||||||
import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator;
|
import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator;
|
||||||
|
@ -115,6 +116,7 @@ public class DefaultClientValidationProvider implements ClientValidationProvider
|
||||||
validateUrls(context);
|
validateUrls(context);
|
||||||
validatePairwiseInClientModel(context);
|
validatePairwiseInClientModel(context);
|
||||||
new CibaClientValidation(context).validate();
|
new CibaClientValidation(context).validate();
|
||||||
|
validateJwks(context);
|
||||||
|
|
||||||
return context.toResult();
|
return context.toResult();
|
||||||
}
|
}
|
||||||
|
@ -217,4 +219,12 @@ public class DefaultClientValidationProvider implements ClientValidationProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateJwks(ValidationContext<ClientModel> context) {
|
||||||
|
ClientModel client = context.getObjectToValidate();
|
||||||
|
|
||||||
|
if (Boolean.parseBoolean(client.getAttribute(OIDCConfigAttributes.USE_JWKS_URL))
|
||||||
|
&& Boolean.parseBoolean(client.getAttribute(OIDCConfigAttributes.USE_JWKS_STRING))) {
|
||||||
|
context.addError("jwksUrl", "Illegal to use both jwks_uri and jwks_string", "duplicatedJwksSettings");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -372,7 +372,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
// Utilities for Request Object retrieved by reference from jwks_uri
|
// Utilities for Request Object retrieved by reference from jwks_uri
|
||||||
|
|
||||||
protected KeyPair setupJwks(String algorithm, ClientRepresentation clientRepresentation, ClientResource clientResource) throws Exception {
|
protected KeyPair setupJwksUrl(String algorithm, ClientRepresentation clientRepresentation, ClientResource clientResource) throws Exception {
|
||||||
// generate and register client keypair
|
// generate and register client keypair
|
||||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||||
oidcClientEndpointsResource.generateKeys(algorithm);
|
oidcClientEndpointsResource.generateKeys(algorithm);
|
||||||
|
|
|
@ -2013,7 +2013,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
|
||||||
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId);
|
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId);
|
||||||
ClientRepresentation clientRep = clientResource.toRepresentation();
|
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||||
|
|
||||||
KeyPair keyPair = setupJwks(org.keycloak.crypto.Algorithm.ES256, clientRep, clientResource);
|
KeyPair keyPair = setupJwksUrl(org.keycloak.crypto.Algorithm.ES256, clientRep, clientResource);
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
|
||||||
|
@ -2103,7 +2103,7 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
|
||||||
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId);
|
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientId);
|
||||||
ClientRepresentation clientRep = clientResource.toRepresentation();
|
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||||
|
|
||||||
KeyPair keyPair = setupJwks(org.keycloak.crypto.Algorithm.RS256, clientRep, clientResource);
|
KeyPair keyPair = setupJwksUrl(org.keycloak.crypto.Algorithm.RS256, clientRep, clientResource);
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
|
||||||
|
|
|
@ -232,6 +232,7 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe
|
||||||
|
|
||||||
// Update client with some bad JWKS_URI
|
// Update client with some bad JWKS_URI
|
||||||
response.setJwksUri("http://localhost:4321/non-existent");
|
response.setJwksUri("http://localhost:4321/non-existent");
|
||||||
|
response.setJwks(null);
|
||||||
reg.auth(Auth.token(response.getRegistrationAccessToken()))
|
reg.auth(Auth.token(response.getRegistrationAccessToken()))
|
||||||
.oidc().update(response);
|
.oidc().update(response);
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ import org.keycloak.crypto.SignatureSignerContext;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||||
|
@ -88,6 +89,7 @@ import org.keycloak.testsuite.util.ClientManager;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -297,18 +299,33 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCodeToTokenRequestSuccessES256() throws Exception {
|
public void testCodeToTokenRequestSuccessES256usingJwksUri() throws Exception {
|
||||||
testCodeToTokenRequestSuccess(Algorithm.ES256);
|
testCodeToTokenRequestSuccess(Algorithm.ES256, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCodeToTokenRequestSuccessRS256() throws Exception {
|
public void testCodeToTokenRequestSuccessES256usingJwks() throws Exception {
|
||||||
testCodeToTokenRequestSuccess(Algorithm.RS256);
|
testCodeToTokenRequestSuccess(Algorithm.ES256, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCodeToTokenRequestSuccessPS256() throws Exception {
|
public void testCodeToTokenRequestSuccessRS256usingJwksUri() throws Exception {
|
||||||
testCodeToTokenRequestSuccess(Algorithm.PS256);
|
testCodeToTokenRequestSuccess(Algorithm.RS256, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCodeToTokenRequestSuccessRS256usingJwks() throws Exception {
|
||||||
|
testCodeToTokenRequestSuccess(Algorithm.RS256, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCodeToTokenRequestSuccessPS256usingJwksUri() throws Exception {
|
||||||
|
testCodeToTokenRequestSuccess(Algorithm.PS256, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCodeToTokenRequestSuccessPS256usingJwks() throws Exception {
|
||||||
|
testCodeToTokenRequestSuccess(Algorithm.PS256, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -328,7 +345,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setTokenEndpointAuthSigningAlg(Algorithm.ES256);
|
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setTokenEndpointAuthSigningAlg(Algorithm.ES256);
|
||||||
clientResource.update(clientRep);
|
clientResource.update(clientRep);
|
||||||
|
|
||||||
testCodeToTokenRequestSuccess(Algorithm.ES256);
|
testCodeToTokenRequestSuccess(Algorithm.ES256, true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Assert.fail();
|
Assert.fail();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -352,7 +369,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
String clientSignedToken;
|
String clientSignedToken;
|
||||||
try {
|
try {
|
||||||
// setup Jwks
|
// setup Jwks
|
||||||
KeyPair keyPair = setupJwks(alg, clientRepresentation, clientResource);
|
KeyPair keyPair = setupJwksUrl(alg, clientRepresentation, clientResource);
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
|
||||||
|
@ -370,17 +387,22 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
return clientSignedToken;
|
return clientSignedToken;
|
||||||
} finally {
|
} finally {
|
||||||
// Revert jwks_url settings
|
// Revert jwks_url settings
|
||||||
revertJwksSettings(clientRepresentation, clientResource);
|
revertJwksUriSettings(clientRepresentation, clientResource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testCodeToTokenRequestSuccess(String algorithm) throws Exception {
|
private void testCodeToTokenRequestSuccess(String algorithm, boolean useJwksUri) throws Exception {
|
||||||
ClientRepresentation clientRepresentation = app2;
|
ClientRepresentation clientRepresentation = app2;
|
||||||
ClientResource clientResource = getClient(testRealm.getRealm(), clientRepresentation.getId());
|
ClientResource clientResource = getClient(testRealm.getRealm(), clientRepresentation.getId());
|
||||||
clientRepresentation = clientResource.toRepresentation();
|
clientRepresentation = clientResource.toRepresentation();
|
||||||
try {
|
try {
|
||||||
// setup Jwks
|
// setup Jwks
|
||||||
KeyPair keyPair = setupJwks(algorithm, clientRepresentation, clientResource);
|
KeyPair keyPair;
|
||||||
|
if (useJwksUri) {
|
||||||
|
keyPair = setupJwksUrl(algorithm, clientRepresentation, clientResource);
|
||||||
|
} else {
|
||||||
|
keyPair = setupJwks(algorithm, clientRepresentation, clientResource);
|
||||||
|
}
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
|
||||||
|
@ -402,8 +424,12 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
.detail(Details.CLIENT_AUTH_METHOD, JWTClientAuthenticator.PROVIDER_ID)
|
.detail(Details.CLIENT_AUTH_METHOD, JWTClientAuthenticator.PROVIDER_ID)
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
// Revert jwks_url settings
|
// Revert jwks settings
|
||||||
revertJwksSettings(clientRepresentation, clientResource);
|
if (useJwksUri) {
|
||||||
|
revertJwksUriSettings(clientRepresentation, clientResource);
|
||||||
|
} else {
|
||||||
|
revertJwksSettings(clientRepresentation, clientResource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,7 +464,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
try {
|
try {
|
||||||
// setup Jwks
|
// setup Jwks
|
||||||
String signingAlgorithm = Algorithm.PS256;
|
String signingAlgorithm = Algorithm.PS256;
|
||||||
KeyPair keyPair = setupJwks(signingAlgorithm, false, clientRepresentation, clientResource);
|
KeyPair keyPair = setupJwksUrl(signingAlgorithm, false, clientRepresentation, clientResource);
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
|
||||||
|
@ -449,7 +475,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
assertEquals(200, response.getStatusCode());
|
assertEquals(200, response.getStatusCode());
|
||||||
} finally {
|
} finally {
|
||||||
// Revert jwks_url settings
|
// Revert jwks_url settings
|
||||||
revertJwksSettings(clientRepresentation, clientResource);
|
revertJwksUriSettings(clientRepresentation, clientResource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,7 +487,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
try {
|
try {
|
||||||
// send a JWS using the default algorithm
|
// send a JWS using the default algorithm
|
||||||
String signingAlgorithm = Algorithm.RS256;
|
String signingAlgorithm = Algorithm.RS256;
|
||||||
KeyPair keyPair = setupJwks(signingAlgorithm, false, clientRepresentation, clientResource);
|
KeyPair keyPair = setupJwksUrl(signingAlgorithm, false, clientRepresentation, clientResource);
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
oauth.clientId("client2");
|
oauth.clientId("client2");
|
||||||
|
@ -483,7 +509,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
assertEquals("invalid signature algorithm", response.getErrorDescription());
|
assertEquals("invalid signature algorithm", response.getErrorDescription());
|
||||||
} finally {
|
} finally {
|
||||||
// Revert jwks_url settings
|
// Revert jwks_url settings
|
||||||
revertJwksSettings(clientRepresentation, clientResource);
|
revertJwksUriSettings(clientRepresentation, clientResource);
|
||||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setTokenEndpointAuthSigningAlg(null);
|
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setTokenEndpointAuthSigningAlg(null);
|
||||||
clientResource.update(clientRepresentation);
|
clientResource.update(clientRepresentation);
|
||||||
}
|
}
|
||||||
|
@ -510,7 +536,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
clientRepresentation = clientResource.toRepresentation();
|
clientRepresentation = clientResource.toRepresentation();
|
||||||
try {
|
try {
|
||||||
// setup Jwks
|
// setup Jwks
|
||||||
KeyPair keyPair = setupJwks(algorithm, clientRepresentation, clientResource);
|
KeyPair keyPair = setupJwksUrl(algorithm, clientRepresentation, clientResource);
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
|
||||||
|
@ -536,7 +562,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
// Revert jwks_url settings
|
// Revert jwks_url settings
|
||||||
revertJwksSettings(clientRepresentation, clientResource);
|
revertJwksUriSettings(clientRepresentation, clientResource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -870,22 +896,27 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
ClientRepresentation clientRepresentation = app2;
|
ClientRepresentation clientRepresentation = app2;
|
||||||
ClientResource clientResource = getClient(testRealm.getRealm(), clientRepresentation.getId());
|
ClientResource clientResource = getClient(testRealm.getRealm(), clientRepresentation.getId());
|
||||||
clientRepresentation = clientResource.toRepresentation();
|
clientRepresentation = clientResource.toRepresentation();
|
||||||
|
try {
|
||||||
|
KeyPair keyPair = setupJwksUrl(Algorithm.PS256, clientRepresentation, clientResource);
|
||||||
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
JsonWebToken assertion = createRequestToken(app2.getClientId(), getRealmInfoUrl());
|
||||||
|
|
||||||
KeyPair keyPair = setupJwks(Algorithm.PS256, clientRepresentation, clientResource);
|
assertion.audience(endpointUrl);
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
|
||||||
JsonWebToken assertion = createRequestToken(app2.getClientId(), getRealmInfoUrl());
|
|
||||||
|
|
||||||
assertion.audience(endpointUrl);
|
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
||||||
|
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
|
||||||
|
parameters
|
||||||
|
.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
||||||
|
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION,
|
||||||
|
createSignledRequestToken(privateKey, publicKey, Algorithm.PS256, assertion)));
|
||||||
|
|
||||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
try (CloseableHttpResponse resp = sendRequest(oauth.getServiceAccountUrl(), parameters)) {
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
|
OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(resp);
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
assertNotNull(response.getAccessToken());
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, createSignledRequestToken(privateKey, publicKey, Algorithm.PS256, assertion)));
|
}
|
||||||
|
} finally {
|
||||||
try (CloseableHttpResponse resp = sendRequest(oauth.getServiceAccountUrl(), parameters)) {
|
revertJwksUriSettings(clientRepresentation, clientResource);
|
||||||
OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(resp);
|
|
||||||
assertNotNull(response.getAccessToken());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -895,21 +926,27 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
ClientResource clientResource = getClient(testRealm.getRealm(), clientRepresentation.getId());
|
ClientResource clientResource = getClient(testRealm.getRealm(), clientRepresentation.getId());
|
||||||
clientRepresentation = clientResource.toRepresentation();
|
clientRepresentation = clientResource.toRepresentation();
|
||||||
|
|
||||||
KeyPair keyPair = setupJwks(Algorithm.PS256, clientRepresentation, clientResource);
|
try {
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
KeyPair keyPair = setupJwksUrl(Algorithm.PS256, clientRepresentation, clientResource);
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
JsonWebToken assertion = createRequestToken(app2.getClientId(), getRealmInfoUrl());
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
JsonWebToken assertion = createRequestToken(app2.getClientId(), getRealmInfoUrl());
|
||||||
|
|
||||||
assertion.audience("https://as.other.org");
|
assertion.audience("https://as.other.org");
|
||||||
|
|
||||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
|
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS));
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
parameters
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, createSignledRequestToken(privateKey, publicKey, Algorithm.PS256, assertion)));
|
.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
||||||
|
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION,
|
||||||
|
createSignledRequestToken(privateKey, publicKey, Algorithm.PS256, assertion)));
|
||||||
|
|
||||||
try (CloseableHttpResponse resp = sendRequest(oauth.getServiceAccountUrl(), parameters)) {
|
try (CloseableHttpResponse resp = sendRequest(oauth.getServiceAccountUrl(), parameters)) {
|
||||||
OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(resp);
|
OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(resp);
|
||||||
assertNull(response.getAccessToken());
|
assertNull(response.getAccessToken());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
revertJwksUriSettings(clientRepresentation, clientResource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1092,7 +1129,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
clientRepresentation = clientResource.toRepresentation();
|
clientRepresentation = clientResource.toRepresentation();
|
||||||
try {
|
try {
|
||||||
// setup Jwks
|
// setup Jwks
|
||||||
KeyPair keyPair = setupJwks(algorithm, clientRepresentation, clientResource);
|
KeyPair keyPair = setupJwksUrl(algorithm, clientRepresentation, clientResource);
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
|
||||||
|
@ -1118,7 +1155,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
// Revert jwks_url settings
|
// Revert jwks_url settings
|
||||||
revertJwksSettings(clientRepresentation, clientResource);
|
revertJwksUriSettings(clientRepresentation, clientResource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1133,7 +1170,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
clientRepresentation = clientResource.toRepresentation();
|
clientRepresentation = clientResource.toRepresentation();
|
||||||
try {
|
try {
|
||||||
// setup Jwks
|
// setup Jwks
|
||||||
setupJwks(algorithm, clientRepresentation, clientResource);
|
setupJwksUrl(algorithm, clientRepresentation, clientResource);
|
||||||
|
|
||||||
// test
|
// test
|
||||||
oauth.clientId("client2");
|
oauth.clientId("client2");
|
||||||
|
@ -1151,7 +1188,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
// Revert jwks_url settings
|
// Revert jwks_url settings
|
||||||
revertJwksSettings(clientRepresentation, clientResource);
|
revertJwksUriSettings(clientRepresentation, clientResource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1319,11 +1356,11 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
return keyStore;
|
return keyStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyPair setupJwks(String algorithm, ClientRepresentation clientRepresentation, ClientResource clientResource) throws Exception {
|
private KeyPair setupJwksUrl(String algorithm, ClientRepresentation clientRepresentation, ClientResource clientResource) throws Exception {
|
||||||
return setupJwks(algorithm, true, clientRepresentation, clientResource);
|
return setupJwksUrl(algorithm, true, clientRepresentation, clientResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyPair setupJwks(String algorithm, boolean advertiseJWKAlgorithm, ClientRepresentation clientRepresentation, ClientResource clientResource) throws Exception {
|
private KeyPair setupJwksUrl(String algorithm, boolean advertiseJWKAlgorithm, ClientRepresentation clientRepresentation, ClientResource clientResource) throws Exception {
|
||||||
// generate and register client keypair
|
// generate and register client keypair
|
||||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||||
oidcClientEndpointsResource.generateKeys(algorithm, advertiseJWKAlgorithm);
|
oidcClientEndpointsResource.generateKeys(algorithm, advertiseJWKAlgorithm);
|
||||||
|
@ -1342,12 +1379,39 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
|
||||||
return keyPair;
|
return keyPair;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void revertJwksSettings(ClientRepresentation clientRepresentation, ClientResource clientResource) {
|
private KeyPair setupJwks(String algorithm, ClientRepresentation clientRepresentation, ClientResource clientResource)
|
||||||
|
throws Exception {
|
||||||
|
// generate and register client keypair
|
||||||
|
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||||
|
oidcClientEndpointsResource.generateKeys(algorithm);
|
||||||
|
Map<String, String> generatedKeys = oidcClientEndpointsResource.getKeysAsBase64();
|
||||||
|
KeyPair keyPair = getKeyPairFromGeneratedBase64(generatedKeys, algorithm);
|
||||||
|
|
||||||
|
// use and set JWKS
|
||||||
|
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setUseJwksString(true);
|
||||||
|
JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks();
|
||||||
|
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation)
|
||||||
|
.setJwksString(JsonSerialization.writeValueAsString(keySet));
|
||||||
|
clientResource.update(clientRepresentation);
|
||||||
|
|
||||||
|
// set time offset, so that new keys are downloaded
|
||||||
|
setTimeOffset(20);
|
||||||
|
|
||||||
|
return keyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void revertJwksUriSettings(ClientRepresentation clientRepresentation, ClientResource clientResource) {
|
||||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setUseJwksUrl(false);
|
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setUseJwksUrl(false);
|
||||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setJwksUrl(null);
|
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setJwksUrl(null);
|
||||||
clientResource.update(clientRepresentation);
|
clientResource.update(clientRepresentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void revertJwksSettings(ClientRepresentation clientRepresentation, ClientResource clientResource) {
|
||||||
|
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setUseJwksString(false);
|
||||||
|
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setJwksString(null);
|
||||||
|
clientResource.update(clientRepresentation);
|
||||||
|
}
|
||||||
|
|
||||||
private KeyPair getKeyPairFromGeneratedBase64(Map<String, String> generatedKeys, String algorithm) throws Exception {
|
private KeyPair getKeyPairFromGeneratedBase64(Map<String, String> generatedKeys, String algorithm) throws Exception {
|
||||||
// It seems that PemUtils.decodePrivateKey, decodePublicKey can only treat RSA type keys, not EC type keys. Therefore, these are not used.
|
// It seems that PemUtils.decodePrivateKey, decodePublicKey can only treat RSA type keys, not EC type keys. Therefore, these are not used.
|
||||||
String privateKeyBase64 = generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY);
|
String privateKeyBase64 = generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY);
|
||||||
|
|
|
@ -487,9 +487,13 @@ kid.tooltip=KID (Key ID) of the client public key from imported JWKS.
|
||||||
token-endpoint-auth-signing-alg=Signature Algorithm
|
token-endpoint-auth-signing-alg=Signature Algorithm
|
||||||
token-endpoint-auth-signing-alg.tooltip=JWA algorithm, which the client needs to use when signing a JWT for authentication. If left blank, the client is allowed to use any algorithm.
|
token-endpoint-auth-signing-alg.tooltip=JWA algorithm, which the client needs to use when signing a JWT for authentication. If left blank, the client is allowed to use any algorithm.
|
||||||
use-jwks-url=Use JWKS URL
|
use-jwks-url=Use JWKS URL
|
||||||
use-jwks-url.tooltip=If the switch is on, client public keys will be downloaded from given JWKS URL. This allows great flexibility because new keys will be always re-downloaded again when client generates new keypair. If the switch is off, public key (or certificate) from the Keycloak DB is used, so when client keypair changes, you always need to import new key (or certificate) to the Keycloak DB as well.
|
use-jwks-url.tooltip=If the switch is on, client public keys will be downloaded from given JWKS URL. This allows great flexibility because new keys will be always re-downloaded again when client generates new keypair. If the switch is off, public key (or certificate) from the Keycloak DB is used, so when client keypair changes, you always need to import new key (or certificate) to the Keycloak DB as well. This switch is mutually exclusive with the switch "Use JWKS".
|
||||||
jwks-url=JWKS URL
|
jwks-url=JWKS URL
|
||||||
jwks-url.tooltip=URL where client keys in JWK format are stored. See JWK specification for more details. If you use Keycloak client adapter with "jwt" credential, you can use URL of your app with '/k_jwks' suffix. For example 'http://www.myhost.com/myapp/k_jwks' .
|
jwks-url.tooltip=URL where client keys in JWK format are stored. See JWK specification for more details. If you use Keycloak client adapter with "jwt" credential, you can use URL of your app with '/k_jwks' suffix. For example 'http://www.myhost.com/myapp/k_jwks' .
|
||||||
|
use-jwks-string=Use JWKS
|
||||||
|
use-jwks-string.tooltip=If the switch is on, client public keys will be configurable in JWKS. This switch is mutually exclusive with the switch "Use JWKS URL".
|
||||||
|
jwks-string=JWKS
|
||||||
|
jwks-string.tooltip=Client keys in JWK format. See JWK specification for more details.
|
||||||
pkce-enabled=Use PKCE
|
pkce-enabled=Use PKCE
|
||||||
pkce-enabled.tooltip=Use PKCE (Proof of Key-code exchange) for IdP Brokering
|
pkce-enabled.tooltip=Use PKCE (Proof of Key-code exchange) for IdP Brokering
|
||||||
pkce-method=PKCE Method
|
pkce-method=PKCE Method
|
||||||
|
|
|
@ -41,6 +41,8 @@ pairwiseMalformedSectorIdentifierURI=Malformed Sector Identifier URI.
|
||||||
pairwiseFailedToGetRedirectURIs=Failed to get redirect URIs from the Sector Identifier URI.
|
pairwiseFailedToGetRedirectURIs=Failed to get redirect URIs from the Sector Identifier URI.
|
||||||
pairwiseRedirectURIsMismatch=Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.
|
pairwiseRedirectURIsMismatch=Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.
|
||||||
|
|
||||||
|
duplicatedJwksSettings=The "Use JWKS" switch and the switch "Use JWKS URL" cannot be ON at the same time.
|
||||||
|
|
||||||
error-invalid-value=Invalid value.
|
error-invalid-value=Invalid value.
|
||||||
error-invalid-blank=Please specify value.
|
error-invalid-blank=Please specify value.
|
||||||
error-empty=Please specify value.
|
error-empty=Please specify value.
|
||||||
|
|
|
@ -619,8 +619,26 @@ module.controller('ClientOidcKeyCtrl', function($scope, $location, realm, client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.switchChange = function() {
|
if ($scope.client.attributes["use.jwks.string"]) {
|
||||||
|
if ($scope.client.attributes["use.jwks.string"] == "true") {
|
||||||
|
$scope.useJwksString = true;
|
||||||
|
} else {
|
||||||
|
$scope.useJwksString = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.jwksUrlSwitchChange = function() {
|
||||||
$scope.changed = true;
|
$scope.changed = true;
|
||||||
|
if ($scope.useJwksUrl == false) {
|
||||||
|
$scope.useJwksString = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.jwksStringSwitchChange = function() {
|
||||||
|
$scope.changed = true;
|
||||||
|
if ($scope.useJwksString == false) {
|
||||||
|
$scope.useJwksUrl = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.save = function() {
|
$scope.save = function() {
|
||||||
|
@ -631,6 +649,12 @@ module.controller('ClientOidcKeyCtrl', function($scope, $location, realm, client
|
||||||
$scope.client.attributes["use.jwks.url"] = "false";
|
$scope.client.attributes["use.jwks.url"] = "false";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($scope.useJwksString == true) {
|
||||||
|
$scope.client.attributes["use.jwks.string"] = "true";
|
||||||
|
} else {
|
||||||
|
$scope.client.attributes["use.jwks.string"] = "false";
|
||||||
|
}
|
||||||
|
|
||||||
Client.update({
|
Client.update({
|
||||||
realm : realm.realm,
|
realm : realm.realm,
|
||||||
client : client.id
|
client : client.id
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="useJwksUrl">{{:: 'use-jwks-url' | translate}}</label>
|
<label class="col-md-2 control-label" for="useJwksUrl">{{:: 'use-jwks-url' | translate}}</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<input ng-model="useJwksUrl" name="useJwksUrl" id="useJwksUrl" ng-click="switchChange()" onoffswitch
|
<input ng-model="useJwksUrl" name="useJwksUrl" id="useJwksUrl" ng-click="jwksUrlSwitchChange()" onoffswitch
|
||||||
on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'use-jwks-url.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'use-jwks-url.tooltip' | translate}}</kc-tooltip>
|
||||||
|
@ -26,7 +26,25 @@
|
||||||
<kc-tooltip>{{:: 'jwks-url.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'jwks-url.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-ng-show="!useJwksUrl">
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="useJwksString">{{:: 'use-jwks-string' | translate}}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input ng-model="useJwksString" name="useJwksString" id="useJwksString" ng-click="jwksStringSwitchChange()"
|
||||||
|
onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'use-jwks-string.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="useJwksString">
|
||||||
|
<label class="col-md-2 control-label" for="jwksString">{{:: 'jwks-string' | translate}}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<textarea type="text" id="jwksString" name="jwksString" class="form-control" rows="5" kc-select-action="click"
|
||||||
|
data-ng-model="client.attributes['jwks.string']"></textarea>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'jwks-string.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-ng-show="!useJwksUrl && !useJwksString">
|
||||||
|
|
||||||
<div class="form-group" data-ng-show="signingKeyInfo.certificate">
|
<div class="form-group" data-ng-show="signingKeyInfo.certificate">
|
||||||
<label class="col-md-2 control-label" for="signingCert">{{:: 'certificate' | translate}}</label>
|
<label class="col-md-2 control-label" for="signingCert">{{:: 'certificate' | translate}}</label>
|
||||||
|
@ -77,8 +95,8 @@
|
||||||
<div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
|
<div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
|
||||||
<button class="btn btn-default" type="submit" data-ng-click="generateSigningKey()">{{::
|
<button class="btn btn-default" type="submit" data-ng-click="generateSigningKey()">{{::
|
||||||
'gen-new-keys-and-cert' | translate}}</button>
|
'gen-new-keys-and-cert' | translate}}</button>
|
||||||
<button data-ng-disabled="useJwksUrl" class="btn btn-default" type="submit" data-ng-click="importCertificate()">{{::
|
<button data-ng-disabled="useJwksUrl || useJwksString" class="btn btn-default" type="submit"
|
||||||
'import-certificate' | translate}}</button>
|
data-ng-click="importCertificate()">{{:: 'import-certificate' | translate}}</button>
|
||||||
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||||
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue