keycloak-uncached

Changes

Details

diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
index 6503e67..d68c7cb 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
@@ -51,7 +51,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
         }
 
         String clientKeystoreType = (String) cfg.get("client-keystore-type");
-        KeystoreUtil.KeystoreFormat clientKeystoreFormat = clientKeystoreType==null ? KeystoreUtil.KeystoreFormat.JKS : Enum.valueOf(KeystoreUtil.KeystoreFormat.class, clientKeystoreType);
+        KeystoreUtil.KeystoreFormat clientKeystoreFormat = clientKeystoreType==null ? KeystoreUtil.KeystoreFormat.JKS : Enum.valueOf(KeystoreUtil.KeystoreFormat.class, clientKeystoreType.toUpperCase());
 
         String clientKeystorePassword =  (String) cfg.get("client-keystore-password");
         if (clientKeystorePassword == null) {
@@ -69,8 +69,23 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
         }
         this.privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
 
-        Integer tokenExp = (Integer) cfg.get("token-timeout");
-        this.tokenTimeout = (tokenExp==null) ? 10 : tokenExp;
+        this.tokenTimeout = asInt(cfg, "token-timeout", 10);
+    }
+
+    // TODO: Generic method for this?
+    private Integer asInt(Map<String, Object> cfg, String cfgKey, int defaultValue) {
+        Object cfgObj = cfg.get(cfgKey);
+        if (cfgObj == null) {
+            return defaultValue;
+        }
+
+        if (cfgObj instanceof String) {
+            return Integer.parseInt(cfgObj.toString());
+        } else if (cfgObj instanceof Number) {
+            return ((Number) cfgObj).intValue();
+        } else {
+            throw new IllegalArgumentException("Can't parse " + cfgKey + " from the config. Value is " + cfgObj);
+        }
     }
 
     @Override
diff --git a/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java b/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java
index af9e74f..845da8e 100755
--- a/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java
+++ b/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java
@@ -85,7 +85,19 @@ public final class KeycloakAdapterConfigService {
         }
 
         String credentialName = credentialNameFromOp(operation);
-        credentials.get(credentialName).set(model.get("value").asString());
+        if (!credentialName.contains(".")) {
+            credentials.get(credentialName).set(model.get("value").asString());
+        } else {
+            String[] parts = credentialName.split("\\.");
+            String provider = parts[0];
+            String property = parts[1];
+            ModelNode credential = credentials.get(provider);
+            if (!credential.isDefined()) {
+                credential = new ModelNode();
+            }
+            credential.get(property).set(model.get("value").asString());
+            credentials.set(provider, credential);
+        }
 
         ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
         deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
diff --git a/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemParser.java b/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemParser.java
index 5c61e55..42b3996 100755
--- a/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemParser.java
+++ b/integration/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemParser.java
@@ -34,7 +34,10 @@ import javax.xml.stream.XMLStreamConstants;
 import javax.xml.stream.XMLStreamException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * The subsystem parser, which uses stax to read and write to and from xml
@@ -125,12 +128,48 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
 
     public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
         String name = readNameAttribute(reader);
+
+        Map<String, String> values = new HashMap<>();
+        String textValue = null;
+        while (reader.hasNext()) {
+            int next = reader.next();
+            if (next == CHARACTERS) {
+                // text value of credential element (like for "secret" )
+                String text = reader.getText();
+                if (text == null || text.trim().isEmpty()) {
+                    continue;
+                }
+                textValue = text;
+            } else if (next == START_ELEMENT) {
+                String key = reader.getLocalName();
+                reader.next();
+                String value = reader.getText();
+                reader.next();
+
+                values.put(key, value);
+            } else if (next == END_ELEMENT) {
+                break;
+            }
+        }
+
+        if (textValue != null) {
+            ModelNode addCredential = getCredentialToAdd(parent, name, textValue);
+            credentialsToAdd.add(addCredential);
+        } else {
+            for (Map.Entry<String, String> entry : values.entrySet()) {
+                ModelNode addCredential = getCredentialToAdd(parent, name + "." + entry.getKey(), entry.getValue());
+                credentialsToAdd.add(addCredential);
+            }
+        }
+    }
+
+    private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
         ModelNode addCredential = new ModelNode();
         addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
         PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
         addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
-        addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText());
-        credentialsToAdd.add(addCredential);
+        addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
+        return addCredential;
     }
 
     // expects that the current tag will have one single attribute called "name"
@@ -199,11 +238,43 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
     }
 
     private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
+        Map<String, Object> parsed = new LinkedHashMap<>();
         for (Property credential : credentials.asPropertyList()) {
+            String credName = credential.getName();
+            String credValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
+
+            if (credName.contains(".")) {
+                String[] parts = credName.split("\\.");
+                String provider = parts[0];
+                String propKey = parts[1];
+
+                Map<String, String> currentProviderMap = (Map<String, String>) parsed.get(provider);
+                if (currentProviderMap == null) {
+                    currentProviderMap = new LinkedHashMap<>();
+                    parsed.put(provider, currentProviderMap);
+                }
+                currentProviderMap.put(propKey, credValue);
+            } else {
+                parsed.put(credName, credValue);
+            }
+        }
+
+        for (Map.Entry<String, Object> entry : parsed.entrySet()) {
             writer.writeStartElement(CredentialDefinition.TAG_NAME);
-            writer.writeAttribute("name", credential.getName());
-            String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
-            writeCharacters(writer, credentialValue);
+            writer.writeAttribute("name", entry.getKey());
+
+            Object value = entry.getValue();
+            if (value instanceof String) {
+                writeCharacters(writer, (String) value);
+            } else {
+                Map<String, String> credentialProps = (Map<String, String>) value;
+                for (Map.Entry<String, String> prop : credentialProps.entrySet()) {
+                    writer.writeStartElement(prop.getKey());
+                    writeCharacters(writer, prop.getValue());
+                    writer.writeEndElement();
+                }
+            }
+
             writer.writeEndElement();
         }
     }
diff --git a/integration/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd b/integration/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd
index 269b323..75de38a 100755
--- a/integration/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd
+++ b/integration/as7-eap6/as7-subsystem/src/main/resources/schema/keycloak_1_1.xsd
@@ -94,12 +94,11 @@
             </xs:annotation>
         </xs:attribute>
     </xs:complexType>
-    
-    <xs:complexType name="credential-type">
-        <xs:simpleContent>
-            <xs:extension base="xs:string">
-                <xs:attribute name="name" type="xs:string" />
-            </xs:extension>
-        </xs:simpleContent>
+
+    <xs:complexType name="credential-type" mixed="true">
+        <xs:sequence maxOccurs="unbounded" minOccurs="0">
+            <xs:any processContents="lax"></xs:any>
+        </xs:sequence>
+        <xs:attribute name="name" type="xs:string" use="required" />
     </xs:complexType>
 </xs:schema>
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java
index 4843534..2a0b93c 100755
--- a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java
@@ -84,7 +84,19 @@ public final class KeycloakAdapterConfigService {
         }
 
         String credentialName = credentialNameFromOp(operation);
-        credentials.get(credentialName).set(model.get("value").asString());
+        if (!credentialName.contains(".")) {
+            credentials.get(credentialName).set(model.get("value").asString());
+        } else {
+            String[] parts = credentialName.split("\\.");
+            String provider = parts[0];
+            String property = parts[1];
+            ModelNode credential = credentials.get(provider);
+            if (!credential.isDefined()) {
+                credential = new ModelNode();
+            }
+            credential.get(property).set(model.get("value").asString());
+            credentials.set(provider, credential);
+        }
 
         ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
         deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java
index efa260b..a261bc4 100755
--- a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java
@@ -35,7 +35,10 @@ import javax.xml.stream.XMLStreamConstants;
 import javax.xml.stream.XMLStreamException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * The subsystem parser, which uses stax to read and write to and from xml
@@ -126,12 +129,48 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
 
     public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
         String name = readNameAttribute(reader);
+
+        Map<String, String> values = new HashMap<>();
+        String textValue = null;
+        while (reader.hasNext()) {
+            int next = reader.next();
+            if (next == CHARACTERS) {
+                // text value of credential element (like for "secret" )
+                String text = reader.getText();
+                if (text == null || text.trim().isEmpty()) {
+                    continue;
+                }
+                textValue = text;
+            } else if (next == START_ELEMENT) {
+                String key = reader.getLocalName();
+                reader.next();
+                String value = reader.getText();
+                reader.next();
+
+                values.put(key, value);
+            } else if (next == END_ELEMENT) {
+                break;
+            }
+        }
+
+        if (textValue != null) {
+            ModelNode addCredential = getCredentialToAdd(parent, name, textValue);
+            credentialsToAdd.add(addCredential);
+        } else {
+            for (Map.Entry<String, String> entry : values.entrySet()) {
+                ModelNode addCredential = getCredentialToAdd(parent, name + "." + entry.getKey(), entry.getValue());
+                credentialsToAdd.add(addCredential);
+            }
+        }
+    }
+
+    private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
         ModelNode addCredential = new ModelNode();
         addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
         PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
         addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
-        addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText());
-        credentialsToAdd.add(addCredential);
+        addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
+        return addCredential;
     }
 
     // expects that the current tag will have one single attribute called "name"
@@ -200,11 +239,43 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
     }
 
     private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
+        Map<String, Object> parsed = new LinkedHashMap<>();
         for (Property credential : credentials.asPropertyList()) {
+            String credName = credential.getName();
+            String credValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
+
+            if (credName.contains(".")) {
+                String[] parts = credName.split("\\.");
+                String provider = parts[0];
+                String propKey = parts[1];
+
+                Map<String, String> currentProviderMap = (Map<String, String>) parsed.get(provider);
+                if (currentProviderMap == null) {
+                    currentProviderMap = new LinkedHashMap<>();
+                    parsed.put(provider, currentProviderMap);
+                }
+                currentProviderMap.put(propKey, credValue);
+            } else {
+                parsed.put(credName, credValue);
+            }
+        }
+
+        for (Map.Entry<String, Object> entry : parsed.entrySet()) {
             writer.writeStartElement(CredentialDefinition.TAG_NAME);
-            writer.writeAttribute("name", credential.getName());
-            String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
-            writeCharacters(writer, credentialValue);
+            writer.writeAttribute("name", entry.getKey());
+
+            Object value = entry.getValue();
+            if (value instanceof String) {
+                writeCharacters(writer, (String) value);
+            } else {
+                Map<String, String> credentialProps = (Map<String, String>) value;
+                for (Map.Entry<String, String> prop : credentialProps.entrySet()) {
+                    writer.writeStartElement(prop.getKey());
+                    writeCharacters(writer, prop.getValue());
+                    writer.writeEndElement();
+                }
+            }
+
             writer.writeEndElement();
         }
     }
diff --git a/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
index 269b323..75de38a 100755
--- a/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
+++ b/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -94,12 +94,11 @@
             </xs:annotation>
         </xs:attribute>
     </xs:complexType>
-    
-    <xs:complexType name="credential-type">
-        <xs:simpleContent>
-            <xs:extension base="xs:string">
-                <xs:attribute name="name" type="xs:string" />
-            </xs:extension>
-        </xs:simpleContent>
+
+    <xs:complexType name="credential-type" mixed="true">
+        <xs:sequence maxOccurs="unbounded" minOccurs="0">
+            <xs:any processContents="lax"></xs:any>
+        </xs:sequence>
+        <xs:attribute name="name" type="xs:string" use="required" />
     </xs:complexType>
 </xs:schema>
diff --git a/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java b/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java
index 93e9a59..6089293 100755
--- a/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java
+++ b/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java
@@ -16,6 +16,9 @@
  */
 package org.keycloak.subsystem.wf8.extension;
 
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
 import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
 import org.jboss.dmr.ModelNode;
 import org.junit.Test;
@@ -49,13 +52,46 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest {
         node.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
         node.get("ssl-required").set("external");
         node.get("expose-token").set(true);
+
+        ModelNode jwtCredential = new ModelNode();
+        jwtCredential.get("client-keystore-file").set("/tmp/keystore.jks");
+        jwtCredential.get("client-keystore-password").set("changeit");
         ModelNode credential = new ModelNode();
-        credential.get("password").set("password");
+        credential.get("jwt").set(jwtCredential);
         node.get("credentials").set(credential);
 
         System.out.println("json=" + node.toJSONString(false));
     }
 
+    @Test
+    public void testJsonFromSignedJWTCredentials() {
+        KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
+
+        PathAddress addr = PathAddress.pathAddress(PathElement.pathElement("subsystem", "keycloak"), PathElement.pathElement("secure-deployment", "foo"));
+        ModelNode deploymentOp = new ModelNode();
+        deploymentOp.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+        ModelNode deployment = new ModelNode();
+        deployment.get("realm").set("demo");
+        deployment.get("resource").set("customer-portal");
+        service.addSecureDeployment(deploymentOp, deployment);
+
+        addCredential(addr, service, "secret", "secret1");
+        addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
+        addCredential(addr, service, "jwt.token-timeout", "10");
+
+        System.out.println("Deployment: " + service.getJSON("foo"));
+    }
+
+    private void addCredential(PathAddress parent, KeycloakAdapterConfigService service, String key, String value) {
+        PathAddress credAddr = PathAddress.pathAddress(parent, PathElement.pathElement("credential", key));
+        ModelNode credOp = new ModelNode();
+        credOp.get(ModelDescriptionConstants.OP_ADDR).set(credAddr.toModelNode());
+        ModelNode credential = new ModelNode();
+        credential.get("value").set(value);
+        service.addCredential(credOp, credential);
+    }
+
+
     @Override
     protected String getSubsystemXml() throws IOException {
         return readResource("keycloak-1.1.xml");
diff --git a/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml b/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
index 2d12d88..e512f0e 100644
--- a/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
+++ b/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
@@ -19,6 +19,8 @@
         </realm-public-key>
         <auth-server-url>http://localhost:8080/auth</auth-server-url>
         <ssl-required>EXTERNAL</ssl-required>
-        <credential name="secret">2769a4a2-5be0-454f-838f-f33b7755b667</credential>
+        <credential name="jwt">
+            <client-keystore-file>/tmp/keystore.jks</client-keystore-file>
+        </credential>
     </secure-deployment>
 </subsystem>
\ No newline at end of file
diff --git a/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java b/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
index c6f616a..8eb4b9e 100755
--- a/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
+++ b/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
@@ -84,7 +84,19 @@ public final class KeycloakAdapterConfigService {
         }
 
         String credentialName = credentialNameFromOp(operation);
-        credentials.get(credentialName).set(model.get("value").asString());
+        if (!credentialName.contains(".")) {
+            credentials.get(credentialName).set(model.get("value").asString());
+        } else {
+            String[] parts = credentialName.split("\\.");
+            String provider = parts[0];
+            String property = parts[1];
+            ModelNode credential = credentials.get(provider);
+            if (!credential.isDefined()) {
+                credential = new ModelNode();
+            }
+            credential.get(property).set(model.get("value").asString());
+            credentials.set(provider, credential);
+        }
 
         ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
         deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
diff --git a/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java b/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
index 3c63f7e..9a9e667 100755
--- a/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
+++ b/integration/wildfly/wf9-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
@@ -35,7 +35,10 @@ import javax.xml.stream.XMLStreamConstants;
 import javax.xml.stream.XMLStreamException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * The subsystem parser, which uses stax to read and write to and from xml
@@ -126,12 +129,48 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
 
     public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
         String name = readNameAttribute(reader);
+
+        Map<String, String> values = new HashMap<>();
+        String textValue = null;
+        while (reader.hasNext()) {
+            int next = reader.next();
+            if (next == CHARACTERS) {
+                // text value of credential element (like for "secret" )
+                String text = reader.getText();
+                if (text == null || text.trim().isEmpty()) {
+                    continue;
+                }
+                textValue = text;
+            } else if (next == START_ELEMENT) {
+                String key = reader.getLocalName();
+                reader.next();
+                String value = reader.getText();
+                reader.next();
+
+                values.put(key, value);
+            } else if (next == END_ELEMENT) {
+                break;
+            }
+        }
+
+        if (textValue != null) {
+            ModelNode addCredential = getCredentialToAdd(parent, name, textValue);
+            credentialsToAdd.add(addCredential);
+        } else {
+            for (Map.Entry<String, String> entry : values.entrySet()) {
+                ModelNode addCredential = getCredentialToAdd(parent, name + "." + entry.getKey(), entry.getValue());
+                credentialsToAdd.add(addCredential);
+            }
+        }
+    }
+
+    private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
         ModelNode addCredential = new ModelNode();
         addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
         PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
         addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
-        addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText());
-        credentialsToAdd.add(addCredential);
+        addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
+        return addCredential;
     }
 
     // expects that the current tag will have one single attribute called "name"
@@ -200,11 +239,43 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
     }
 
     private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
+        Map<String, Object> parsed = new LinkedHashMap<>();
         for (Property credential : credentials.asPropertyList()) {
+            String credName = credential.getName();
+            String credValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
+
+            if (credName.contains(".")) {
+                String[] parts = credName.split("\\.");
+                String provider = parts[0];
+                String propKey = parts[1];
+
+                Map<String, String> currentProviderMap = (Map<String, String>) parsed.get(provider);
+                if (currentProviderMap == null) {
+                    currentProviderMap = new LinkedHashMap<>();
+                    parsed.put(provider, currentProviderMap);
+                }
+                currentProviderMap.put(propKey, credValue);
+            } else {
+                parsed.put(credName, credValue);
+            }
+        }
+
+        for (Map.Entry<String, Object> entry : parsed.entrySet()) {
             writer.writeStartElement(CredentialDefinition.TAG_NAME);
-            writer.writeAttribute("name", credential.getName());
-            String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
-            writeCharacters(writer, credentialValue);
+            writer.writeAttribute("name", entry.getKey());
+
+            Object value = entry.getValue();
+            if (value instanceof String) {
+                writeCharacters(writer, (String) value);
+            } else {
+                Map<String, String> credentialProps = (Map<String, String>) value;
+                for (Map.Entry<String, String> prop : credentialProps.entrySet()) {
+                    writer.writeStartElement(prop.getKey());
+                    writeCharacters(writer, prop.getValue());
+                    writer.writeEndElement();
+                }
+            }
+
             writer.writeEndElement();
         }
     }
diff --git a/integration/wildfly/wf9-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/integration/wildfly/wf9-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
index 269b323..0abaac1 100755
--- a/integration/wildfly/wf9-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
+++ b/integration/wildfly/wf9-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -95,11 +95,10 @@
         </xs:attribute>
     </xs:complexType>
     
-    <xs:complexType name="credential-type">
-        <xs:simpleContent>
-            <xs:extension base="xs:string">
-                <xs:attribute name="name" type="xs:string" />
-            </xs:extension>
-        </xs:simpleContent>
+    <xs:complexType name="credential-type" mixed="true">
+        <xs:sequence maxOccurs="unbounded" minOccurs="0">
+            <xs:any processContents="lax"></xs:any>
+        </xs:sequence>
+        <xs:attribute name="name" type="xs:string" use="required" />
     </xs:complexType>
 </xs:schema>
diff --git a/integration/wildfly/wf9-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java b/integration/wildfly/wf9-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java
index baa131b..dc1e981 100755
--- a/integration/wildfly/wf9-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java
+++ b/integration/wildfly/wf9-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java
@@ -16,6 +16,9 @@
  */
 package org.keycloak.subsystem.adapter.extension;
 
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
 import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
 import org.jboss.dmr.ModelNode;
 import org.junit.Test;
@@ -49,13 +52,45 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest {
         node.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
         node.get("ssl-required").set("external");
         node.get("expose-token").set(true);
+
+        ModelNode jwtCredential = new ModelNode();
+        jwtCredential.get("client-keystore-file").set("/tmp/keystore.jks");
+        jwtCredential.get("client-keystore-password").set("changeit");
         ModelNode credential = new ModelNode();
-        credential.get("password").set("password");
+        credential.get("jwt").set(jwtCredential);
         node.get("credentials").set(credential);
 
         System.out.println("json=" + node.toJSONString(false));
     }
 
+    @Test
+    public void testJsonFromSignedJWTCredentials() {
+        KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
+
+        PathAddress addr = PathAddress.pathAddress(PathElement.pathElement("subsystem", "keycloak"), PathElement.pathElement("secure-deployment", "foo"));
+        ModelNode deploymentOp = new ModelNode();
+        deploymentOp.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+        ModelNode deployment = new ModelNode();
+        deployment.get("realm").set("demo");
+        deployment.get("resource").set("customer-portal");
+        service.addSecureDeployment(deploymentOp, deployment);
+
+        addCredential(addr, service, "secret", "secret1");
+        addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
+        addCredential(addr, service, "jwt.token-timeout", "10");
+
+        System.out.println("Deployment: " + service.getJSON("foo"));
+    }
+
+    private void addCredential(PathAddress parent, KeycloakAdapterConfigService service, String key, String value) {
+        PathAddress credAddr = PathAddress.pathAddress(parent, PathElement.pathElement("credential", key));
+        ModelNode credOp = new ModelNode();
+        credOp.get(ModelDescriptionConstants.OP_ADDR).set(credAddr.toModelNode());
+        ModelNode credential = new ModelNode();
+        credential.get("value").set(value);
+        service.addCredential(credOp, credential);
+    }
+
     @Override
     protected String getSubsystemXml() throws IOException {
         return readResource("keycloak-1.1.xml");
diff --git a/integration/wildfly/wf9-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml b/integration/wildfly/wf9-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
index 2d12d88..e512f0e 100644
--- a/integration/wildfly/wf9-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
+++ b/integration/wildfly/wf9-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
@@ -19,6 +19,8 @@
         </realm-public-key>
         <auth-server-url>http://localhost:8080/auth</auth-server-url>
         <ssl-required>EXTERNAL</ssl-required>
-        <credential name="secret">2769a4a2-5be0-454f-838f-f33b7755b667</credential>
+        <credential name="jwt">
+            <client-keystore-file>/tmp/keystore.jks</client-keystore-file>
+        </credential>
     </secure-deployment>
 </subsystem>
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index fbf530b..fa71557 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -215,10 +215,8 @@ public class ClientManager {
 
         rep.setResource(clientModel.getClientId());
 
-        if (!clientModel.isBearerOnly() && !clientModel.isPublicClient()) {
-            String clientAuthenticator = clientModel.getClientAuthenticatorType();
-            ClientAuthenticatorFactory authenticator = (ClientAuthenticatorFactory) realmManager.getSession().getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, clientAuthenticator);
-            Map<String, Object> adapterConfig = authenticator.getAdapterConfiguration(clientModel);
+        if (showClientCredentialsAdapterConfig(clientModel)) {
+            Map<String, Object> adapterConfig = getClientCredentialsAdapterConfig(clientModel);
             rep.setCredentials(adapterConfig);
         }
 
@@ -240,8 +238,23 @@ public class ClientManager {
         buffer.append("    <ssl-required>").append(realmModel.getSslRequired().name()).append("</ssl-required>\n");
         buffer.append("    <resource>").append(clientModel.getClientId()).append("</resource>\n");
         String cred = clientModel.getSecret();
-        if (!clientModel.isBearerOnly() && !clientModel.isPublicClient()) {
-            buffer.append("    <credential name=\"secret\">").append(cred).append("</credential>\n");
+        if (showClientCredentialsAdapterConfig(clientModel)) {
+            Map<String, Object> adapterConfig = getClientCredentialsAdapterConfig(clientModel);
+            for (Map.Entry<String, Object> entry : adapterConfig.entrySet()) {
+                buffer.append("    <credential name=\"" + entry.getKey() + "\">");
+
+                Object value = entry.getValue();
+                if (value instanceof Map) {
+                    buffer.append("\n");
+                    Map<String, Object> asMap = (Map<String, Object>) value;
+                    for (Map.Entry<String, Object> credEntry : asMap.entrySet()) {
+                        buffer.append("        <" + credEntry.getKey() + ">" + credEntry.getValue().toString() + "</" + credEntry.getKey() + ">\n");
+                    }
+                    buffer.append("    </credential>\n");
+                } else {
+                    buffer.append(value.toString()).append("</credential>\n");
+                }
+            }
         }
         if (clientModel.getRoles().size() > 0) {
             buffer.append("    <use-resource-role-mappings>true</use-resource-role-mappings>\n");
@@ -250,4 +263,22 @@ public class ClientManager {
         return buffer.toString();
     }
 
+    private boolean showClientCredentialsAdapterConfig(ClientModel client) {
+        if (client.isPublicClient()) {
+            return false;
+        }
+
+        if (client.isBearerOnly() && client.getNodeReRegistrationTimeout() <= 0) {
+            return false;
+        }
+
+        return true;
+    }
+
+    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);
+    }
+
 }