diff --git a/model/api/src/main/java/org/keycloak/models/KerberosConstants.java b/core/src/main/java/org/keycloak/constants/KerberosConstants.java
similarity index 70%
rename from model/api/src/main/java/org/keycloak/models/KerberosConstants.java
rename to core/src/main/java/org/keycloak/constants/KerberosConstants.java
index b2a676e8b3..3b7aa60934 100644
--- a/model/api/src/main/java/org/keycloak/models/KerberosConstants.java
+++ b/core/src/main/java/org/keycloak/constants/KerberosConstants.java
@@ -1,4 +1,4 @@
-package org.keycloak.models;
+package org.keycloak.constants;
/**
* @author Marek Posolda
@@ -23,6 +23,12 @@ public class KerberosConstants {
public static final String KRB5_OID = "1.2.840.113554.1.2.2";
+ /**
+ * OID of Kerberos v5 name. See http://www.oid-info.com/get/1.2.840.113554.1.2.2.1
+ */
+ public static final String KRB5_NAME_OID = "1.2.840.113554.1.2.2.1";
+
+
/**
* Configuration federation provider model attributes.
*/
@@ -43,8 +49,13 @@ public class KerberosConstants {
/**
- * Internal attribute used in "state" map . Contains credential from SPNEGO/Kerberos successful authentication
+ * Internal attribute used in "userSession.note" map and in accessToken claims . Contains credential from SPNEGO/Kerberos successful authentication
*/
- public static final String GSS_DELEGATION_CREDENTIAL = "GssDelegationCredential";
+ public static final String GSS_DELEGATION_CREDENTIAL = "gss_delegation_credential";
+
+ /**
+ * Display name for the above in admin console and consent screens
+ */
+ public static final String GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME = "gss delegation credential";
}
diff --git a/core/src/main/java/org/keycloak/util/KerberosSerializationUtils.java b/core/src/main/java/org/keycloak/util/KerberosSerializationUtils.java
new file mode 100644
index 0000000000..296362386a
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/KerberosSerializationUtils.java
@@ -0,0 +1,196 @@
+package org.keycloak.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+
+import javax.security.auth.kerberos.KerberosTicket;
+
+import net.iharder.Base64;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.Oid;
+import org.keycloak.constants.KerberosConstants;
+import org.keycloak.util.reflections.Reflections;
+import sun.security.jgss.GSSCredentialImpl;
+import sun.security.jgss.GSSManagerImpl;
+import sun.security.jgss.krb5.Krb5InitCredential;
+import sun.security.jgss.krb5.Krb5NameElement;
+import sun.security.jgss.spi.GSSCredentialSpi;
+import sun.security.krb5.Credentials;
+
+/**
+ * Provides serialization/deserialization of kerberos {@link org.ietf.jgss.GSSCredential}, so it can be transmitted from auth-server to the application
+ * and used for further calls to kerberos-secured services
+ *
+ * @author Marek Posolda
+ */
+public class KerberosSerializationUtils {
+
+ public static final Oid KRB5_OID;
+ public static final Oid KRB5_NAME_OID;
+ public static final String JAVA_INFO;
+
+ static {
+ try {
+ KRB5_OID = new Oid(KerberosConstants.KRB5_OID);
+ KRB5_NAME_OID = new Oid(KerberosConstants.KRB5_NAME_OID);
+ } catch (GSSException e) {
+ throw new RuntimeException(e);
+ }
+
+ String javaVersion = System.getProperty("java.version");
+ String javaRuntimeVersion = System.getProperty("java.runtime.version");
+ String javaVendor = System.getProperty("java.vendor");
+ String os = System.getProperty("os.version");
+ JAVA_INFO = "Java version: " + javaVersion + ", runtime version: " + javaRuntimeVersion + ", vendor: " + javaVendor + ", os: " + os;
+ }
+
+ private KerberosSerializationUtils() {
+ }
+
+ public static String serializeCredential(GSSCredential gssCredential) throws KerberosSerializationException {
+ try {
+ if (gssCredential == null) {
+ throw new KerberosSerializationException("Null credential given as input");
+ }
+
+ if (!(gssCredential instanceof GSSCredentialImpl)) {
+ throw new KerberosSerializationException("Unknown credential type: " + gssCredential.getClass());
+ }
+
+ GSSCredentialImpl gssCredImpl = (GSSCredentialImpl) gssCredential;
+ Oid[] mechs = gssCredImpl.getMechs();
+
+ for (Oid oid : mechs) {
+ if (oid.equals(KRB5_OID)) {
+ int usage = gssCredImpl.getUsage(oid);
+ boolean initiate = (usage == GSSCredential.INITIATE_ONLY || usage == GSSCredential.INITIATE_AND_ACCEPT);
+
+ GSSCredentialSpi credentialSpi = gssCredImpl.getElement(oid, initiate);
+ if (credentialSpi instanceof Krb5InitCredential) {
+ Krb5InitCredential credential = (Krb5InitCredential) credentialSpi;
+ KerberosTicket kerberosTicket = new KerberosTicket(credential.getEncoded(),
+ credential.getClient(),
+ credential.getServer(),
+ credential.getSessionKey().getEncoded(),
+ credential.getSessionKeyType(),
+ credential.getFlags(),
+ credential.getAuthTime(),
+ credential.getStartTime(),
+ credential.getEndTime(),
+ credential.getRenewTill(),
+ credential.getClientAddresses());
+ return serialize(kerberosTicket);
+ } else {
+ throw new KerberosSerializationException("Unsupported type of credentialSpi: " + credentialSpi.getClass());
+ }
+ }
+ }
+
+ throw new KerberosSerializationException("Kerberos credential not found. Available mechanisms: " + mechs);
+ } catch (IOException e) {
+ throw new KerberosSerializationException("Exception occured", e);
+ } catch (GSSException e) {
+ throw new KerberosSerializationException("Exception occured", e);
+ }
+ }
+
+
+ public static GSSCredential deserializeCredential(String serializedCred) throws KerberosSerializationException {
+ if (serializedCred == null) {
+ throw new KerberosSerializationException("Null credential given as input. Did you enable kerberos credential delegation for your web browser and mapping of gss credential to access token?");
+ }
+
+ try {
+ Object deserializedCred = deserialize(serializedCred);
+ if (!(deserializedCred instanceof KerberosTicket)) {
+ throw new KerberosSerializationException("Deserialized object is not KerberosTicket! Type is: " + deserializedCred);
+ }
+
+ KerberosTicket ticket = (KerberosTicket) deserializedCred;
+ String fullName = ticket.getClient().getName();
+
+ Method getInstance = Reflections.findDeclaredMethod(Krb5NameElement.class, "getInstance", String.class, Oid.class);
+ Krb5NameElement krb5Name = Reflections.invokeMethod(true, getInstance, Krb5NameElement.class, null, fullName, KRB5_NAME_OID);
+
+ Credentials krb5CredsInternal = new Credentials(
+ ticket.getEncoded(),
+ ticket.getClient().getName(),
+ ticket.getServer().getName(),
+ ticket.getSessionKey().getEncoded(),
+ ticket.getSessionKeyType(),
+ ticket.getFlags(),
+ ticket.getAuthTime(),
+ ticket.getStartTime(),
+ ticket.getEndTime(),
+ ticket.getRenewTill(),
+ ticket.getClientAddresses()
+ );
+
+ Method getInstance2 = Reflections.findDeclaredMethod(Krb5InitCredential.class, "getInstance", Krb5NameElement.class, Credentials.class);
+ Krb5InitCredential initCredential = Reflections.invokeMethod(true, getInstance2, Krb5InitCredential.class, null, krb5Name, krb5CredsInternal);
+
+ GSSManagerImpl manager = (GSSManagerImpl) GSSManager.getInstance();
+ return new GSSCredentialImpl(manager, initCredential);
+ } catch (Exception ioe) {
+ throw new KerberosSerializationException("Exception occured", ioe);
+ }
+ }
+
+
+ private static String serialize(Serializable obj) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutput out = null;
+ try {
+ out = new ObjectOutputStream(bos);
+ out.writeObject(obj);
+ byte[] objBytes = bos.toByteArray();
+ return Base64.encodeBytes(objBytes);
+ } finally {
+ try {
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ private static Object deserialize(String serialized) throws ClassNotFoundException, IOException {
+ byte[] bytes = Base64.decode(serialized);
+ ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ ObjectInput in = null;
+ try {
+ in = new ObjectInputStream(bis);
+ return in.readObject();
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ public static class KerberosSerializationException extends RuntimeException {
+
+ public KerberosSerializationException(String message, Throwable cause) {
+ super(message + ", " + JAVA_INFO, cause);
+ }
+
+ public KerberosSerializationException(String message) {
+ super(message + ", " + JAVA_INFO);
+ }
+ }
+}
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 339afad74c..43907f6efa 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -217,6 +217,12 @@
+
+
+ org.keycloak
+ keycloak-wildfly-extensions
+ ${project.version}
+
\ No newline at end of file
diff --git a/distribution/examples-docs-zip/build.xml b/distribution/examples-docs-zip/build.xml
index 74d503206d..c2790db6ad 100755
--- a/distribution/examples-docs-zip/build.xml
+++ b/distribution/examples-docs-zip/build.xml
@@ -107,6 +107,12 @@
+
+
+
+
+
+
diff --git a/distribution/modules/build.xml b/distribution/modules/build.xml
index 0cd950a8a4..efb3ba4be3 100755
--- a/distribution/modules/build.xml
+++ b/distribution/modules/build.xml
@@ -66,6 +66,10 @@
+
+
+
+
@@ -232,6 +236,8 @@
+
+
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
index c10c776c26..5a35b132aa 100755
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
@@ -14,6 +14,8 @@
+
+
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
index 4cc0d1336f..74e67a7320 100755
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
@@ -47,6 +47,7 @@
+
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-extensions/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-extensions/main/module.xml
new file mode 100755
index 0000000000..9ccc11e01a
--- /dev/null
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-extensions/main/module.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/distribution/modules/src/main/resources/modules/sun/jdk/jgss/main/module.xml b/distribution/modules/src/main/resources/modules/sun/jdk/jgss/main/module.xml
new file mode 100644
index 0000000000..6df03ff4ba
--- /dev/null
+++ b/distribution/modules/src/main/resources/modules/sun/jdk/jgss/main/module.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/modules/kerberos.xml b/docbook/reference/en/en-US/modules/kerberos.xml
index 24b5d9baee..31c8d10bd7 100644
--- a/docbook/reference/en/en-US/modules/kerberos.xml
+++ b/docbook/reference/en/en-US/modules/kerberos.xml
@@ -204,6 +204,40 @@ ktadd -k /tmp/http.keytab HTTP/www.mydomain.org@MYDOMAIN.ORG
+
+
+ Credential delegation
+
+ One scenario supported by Kerberos 5 is credential delegation. In this case when user receives forwardable TGT and authenticates to the web server,
+ then web server might be able to reuse the ticket and forward it to another service secured by Kerberos (for example LDAP server or IMAP server).
+
+
+ The scenario is supported by Keycloak, but there is tricky thing that SPNEGO authentication is done by Keycloak server but
+ GSS credential will need to be used by your application. So you need to enable built-in gss delegation credential protocol mapper
+ in admin console for your application. This will cause that Keycloak will deserialize GSS credential and transmit it to the application
+ in access token. Application will need to deserialize it and use it for further GSS calls against other services.
+
+
+ GSSContext will need to
+ be created with this credential passed to the method GSSManager.createContext for example like this:
+
+
+
+ Note that you also need to configure forwardable kerberos tickets in krb5.conf file
+ and add support for delegated credentials to your browser. See the kerberos example from Keycloak example set for details.
+
+
+
+ Credential delegation has some security implications. So enable the protocol claim and support in browser just if you really need it.
+ It's highly recommended to use it together with HTTPS. See for example
+ this article
+ for details.
+
+
+ Troubleshooting
diff --git a/docbook/reference/en/en-US/modules/providers.xml b/docbook/reference/en/en-US/modules/providers.xml
index 4e2239dcbd..e88bc34e2a 100755
--- a/docbook/reference/en/en-US/modules/providers.xml
+++ b/docbook/reference/en/en-US/modules/providers.xml
@@ -87,33 +87,69 @@ public class MyEventListenerProvider implements EventListenerProvider {
Registering provider implementations
- Keycloak loads provider implementations from the file-system. By default all JARs inside
- standalone/configuration/providers are loaded. This is simple, but requires all providers
- to share the same library. All provides also inherit all classes from the Keycloak class-loader. In the future
- we'll add support to load providers from modules, which allows better control of class isolation.
+ Keycloak can load provider implementations from JBoss Modules or directly from the file-system. Using Modules
+ is recommended as you can control exactly what classes are available to your provider. Any providers loaded
+ from the file-system uses a classloader with the Keycloak classloader as its parent.
-
- To register your provider simply copy the JAR including the ProviderFactory and Provider classes and the
- provider configuration file to standalone/configuration/providers.
-
-
- You can also define multiple provider class-path if you want to create isolated class-loaders. To do this
- edit keycloak-server.json and add more classpath entries to the providers array. For example:
+
+
+ Register a provider using Modules
+
+ To register a provider using Modules first create a module. To do this you have to create a folder inside
+ KEYCLOAK_HOME/modules and add your jar and a module.xml. For example to add the event listener
+ sysout example provider create the folder KEYCLOAK_HOME/modules/org/keycloak/examples/event-sysout/main.
+ Copy event-listener-sysout-example.jar to this folder and create module.xml
+ with the following content:
+
+
+
+
+
+
+
+
+
+
+
+}]]>
+ Next you need to register this module with Keycloak. This is done by editing keycloak-server.json and adding
+ it to the providers:
+
+
+
+
+
+
+ Register a provider using file-system
+
+ To register your provider simply copy the JAR including the ProviderFactory and Provider classes and the
+ provider configuration file to standalone/configuration/providers.
+
+
+ You can also define multiple provider class-path if you want to create isolated class-loaders. To do this
+ edit keycloak-server.json and add more classpath entries to the providers array. For example:
- The above example will create two separate class-loaders for providers. The classpath entries follow the
- same syntax as Java classpath, with ';' separating multiple-entries. Wildcard is also supported allowing
- loading all jars (files with .jar or .JAR extension) in a folder, for example:
+ The above example will create two separate class-loaders for providers. The classpath entries follow the
+ same syntax as Java classpath, with ';' separating multiple-entries. Wildcard is also supported allowing
+ loading all jars (files with .jar or .JAR extension) in a folder, for example:
-
+
+
diff --git a/examples/kerberos/README.md b/examples/kerberos/README.md
new file mode 100644
index 0000000000..5acdb624ba
--- /dev/null
+++ b/examples/kerberos/README.md
@@ -0,0 +1,66 @@
+Keycloak Example - Kerberos Credential Delegation
+=================================================
+
+This example requires that Keycloak is configured with Kerberos/SPNEGO authentication. It's showing how the forwardable TGT is sent from
+the Keycloak auth-server to the application, which deserializes it and authenticates with it to further Kerberized service, which in the example is LDAP server.
+
+Example is using built-in ApacheDS Kerberos server from the keycloak testsuite and the realm with preconfigured federation provider and `gss delegation credential` protocol mapper.
+It also needs to enable forwardable ticket support in Kerberos configuration and your browser.
+
+Detailed steps:
+
+**1)** Build and deploy this sample's WAR file. For this example, deploy on the same server that is running the Keycloak Server, although this is not required for real world scenarios.
+
+
+**2)** Copy `http.keytab` file from the root directory of example to `/tmp` directory (On Linux):
+
+```
+cp http.keytab /tmp/http.keytab
+```
+
+Alternative is to configure different location for `keyTab` property in `kerberosrealm.json` configuration file (On Windows this will be needed).
+Note that in production, keytab file should be in secured location accessible just to the user under which is Keycloak server running.
+
+
+**3)** Run Keycloak server and import `kerberosrealm.json` into it through admin console. This will import realm with sample application
+and configured LDAP federation provider with Kerberos/SPNEGO authentication support enabled and with `gss delegation credential` protocol mapper
+added to the application.
+
+**WARNING:** It's recommended to use JDK8 to run Keycloak server. For JDK7 you may be faced with the bug described [here](http://darranl.blogspot.cz/2014/09/kerberos-encrypteddata-null-key-keytype.html) .
+Alternatively you can use OpenJDK7 but in this case you will need to use aes256-cts-hmac-sha1-96 for both KDC and Kerberos client configuration. For server,
+you can add system property to the maven command when running ApacheDS Kerberos server `-Dkerberos.encTypes=aes256-cts-hmac-sha1-96` (see below) and for
+client add encryption types to configuration file like `/etc/krb5.conf` (but they should be already available. See below).
+
+
+**4)** Run ApacheDS based Kerberos server embedded in Keycloak. Easiest is to checkout keycloak sources, build and then run KerberosEmbeddedServer
+as shown here:
+
+```
+git clone https://github.com/keycloak/keycloak.git
+mvn clean install -DskipTests=true
+cd testsuite/integration
+mvn exec:java -Pkerberos
+```
+
+More details about embedded Kerberos server in [testsuite README](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/README.md#kerberos-server).
+
+
+**5)** Configure Kerberos client (On linux it's in file `/etc/krb5.conf` ). You need to configure `KEYCLOAK.ORG` realm and enable `forwardable` flag, which is needed
+for credential delegation example, as application needs to forward Kerberos ticket and authenticate with it against LDAP server.
+See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/src/main/resources/kerberos/test-krb5.conf) for inspiration.
+
+
+**6)** Configure browser (Firefox, Chrome or other) and enable SPNEGO authentication and credential delegation for `localhost` .
+In Firefox it can be done by adding `localhost` to both `network.negotiate-auth.trusted-uris` and `network.negotiate-auth.delegation-uris` .
+More info in [testsuite README](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/README.md#kerberos-server).
+
+
+**7)** Test the example. Obtain kerberos ticket by running command from CMD (on linux):
+```
+kinit hnelson@KEYCLOAK.ORG
+```
+with password `secret` .
+
+Then in your web browser open `http://localhost:8080/kerberos-portal` . You should be logged-in automatically through SPNEGO without displaying Keycloak login screen.
+Keycloak will also transmit the delegated GSS credential to the application inside access token and application will be able to login with this credential
+to the LDAP server and retrieve some data from it (Actually it just retrieve few simple data about authenticated user himself).
\ No newline at end of file
diff --git a/examples/kerberos/http.keytab b/examples/kerberos/http.keytab
new file mode 100644
index 0000000000..0e7fd96fa7
Binary files /dev/null and b/examples/kerberos/http.keytab differ
diff --git a/examples/kerberos/kerberosrealm.json b/examples/kerberos/kerberosrealm.json
new file mode 100644
index 0000000000..0f044f6ae9
--- /dev/null
+++ b/examples/kerberos/kerberosrealm.json
@@ -0,0 +1,94 @@
+{
+ "id": "kerberos-demo",
+ "realm": "kerberos-demo",
+ "enabled": true,
+ "sslRequired": "external",
+ "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+ "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "requiredCredentials": [ "password", "kerberos" ],
+ "defaultRoles": [ "user" ],
+ "scopeMappings": [
+ {
+ "client": "kerberos-app",
+ "roles": [ "user" ]
+ }
+ ],
+ "applications": [
+ {
+ "name": "kerberos-app",
+ "enabled": true,
+ "baseUrl": "/kerberos-portal",
+ "redirectUris": [
+ "/kerberos-portal/*"
+ ],
+ "adminUrl": "/kerberos-portal",
+ "secret": "password",
+ "protocolMappers": [
+ {
+ "protocolMapper" : "oidc-usermodel-property-mapper",
+ "protocol" : "openid-connect",
+ "name" : "username",
+ "consentText" : "username",
+ "consentRequired" : true,
+ "config" : {
+ "Claim JSON Type" : "String",
+ "user.attribute" : "username",
+ "Token Claim Name" : "preferred_username",
+ "id.token.claim" : "true",
+ "access.token.claim" : "true"
+ }
+ },
+ {
+ "protocolMapper" : "oidc-usersessionmodel-note-mapper",
+ "protocol" : "openid-connect",
+ "name" : "gss delegation credential",
+ "consentText" : "gss delegation credential",
+ "consentRequired" : true,
+ "config" : {
+ "user.session.note" : "gss_delegation_credential",
+ "Token Claim Name" : "gss_delegation_credential",
+ "id.token.claim" : "false",
+ "access.token.claim" : "true"
+ }
+ }
+ ]
+ }
+ ],
+ "roles" : {
+ "realm" : [
+ {
+ "name": "user",
+ "description": "Have User privileges"
+ }
+ ]
+ },
+ "userFederationProviders": [
+ {
+ "displayName": "kerberos-ldap-provider",
+ "providerName": "ldap",
+ "priority": 1,
+ "fullSyncPeriod": -1,
+ "changedSyncPeriod": -1,
+ "config": {
+ "syncRegistrations" : "false",
+ "userAccountControlsAfterPasswordUpdate" : "true",
+ "connectionPooling" : "true",
+ "pagination" : "true",
+ "allowKerberosAuthentication" : "true",
+ "debug" : "true",
+ "editMode" : "WRITABLE",
+ "vendor" : "other",
+ "usernameLDAPAttribute" : "uid",
+ "userObjectClasses" : "inetOrgPerson, organizationalPerson",
+ "connectionUrl" : "ldap://localhost:10389",
+ "baseDn" : "dc=keycloak,dc=org",
+ "userDnSuffix" : "ou=People,dc=keycloak,dc=org",
+ "bindDn" : "uid=admin,ou=system",
+ "bindCredential" : "secret",
+ "kerberosRealm" : "KEYCLOAK.ORG",
+ "serverPrincipal" : "HTTP/localhost@KEYCLOAK.ORG",
+ "keyTab" : "/tmp/http.keytab"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/kerberos/pom.xml b/examples/kerberos/pom.xml
new file mode 100644
index 0000000000..431876e35e
--- /dev/null
+++ b/examples/kerberos/pom.xml
@@ -0,0 +1,83 @@
+
+ 4.0.0
+
+
+ keycloak-parent
+ org.keycloak
+ 1.2.0.Beta1-SNAPSHOT
+ ../../pom.xml
+
+
+ Keycloak Examples - Kerberos Credential Delegation
+ examples-kerberos
+ war
+
+
+ Kerberos Credential Delegation Example
+
+
+
+
+ jboss
+ jboss repo
+ http://repository.jboss.org/nexus/content/groups/public/
+
+
+
+
+
+ org.jboss.spec.javax.servlet
+ jboss-servlet-api_3.0_spec
+ provided
+
+
+ org.keycloak
+ keycloak-core
+ ${project.version}
+ provided
+
+
+ org.keycloak
+ keycloak-adapter-core
+ ${project.version}
+ provided
+
+
+
+
+ kerberos-portal
+
+
+ org.jboss.as.plugins
+ jboss-as-maven-plugin
+
+ false
+
+
+
+ org.wildfly.plugins
+ wildfly-maven-plugin
+
+ false
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ ${maven.compiler.target}
+
+
+
+
+
+
diff --git a/examples/kerberos/src/main/java/org/keycloak/example/kerberos/GSSCredentialsClient.java b/examples/kerberos/src/main/java/org/keycloak/example/kerberos/GSSCredentialsClient.java
new file mode 100644
index 0000000000..3017b1e7a3
--- /dev/null
+++ b/examples/kerberos/src/main/java/org/keycloak/example/kerberos/GSSCredentialsClient.java
@@ -0,0 +1,101 @@
+package org.keycloak.example.kerberos;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.security.sasl.Sasl;
+import javax.servlet.http.HttpServletRequest;
+
+import org.ietf.jgss.GSSCredential;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.constants.KerberosConstants;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.util.KerberosSerializationUtils;
+
+/**
+ * Sample client able to authenticate against ApacheDS LDAP server with Krb5 GSS Credential.
+ *
+ * Credential was previously retrieved from SPNEGO authentication against Keycloak auth-server and transmitted from
+ * Keycloak to the application in OIDC access token
+ *
+ * We can use GSSCredential to further GSS API calls . Note that if you will use GSS API directly, you can
+ * attach GSSCredential when creating GSSContext like this:
+ * GSSContext context = gssManager.createContext(serviceName, KerberosSerializationUtils.KRB5_OID, deserializedGssCredential, GSSContext.DEFAULT_LIFETIME);
+ *
+ * In this example we authenticate against LDAP server, which calls GSS API under the hood when credential is attached to env under Sasl.CREDENTIALS key
+ *
+ * @author Marek Posolda
+ */
+public class GSSCredentialsClient {
+
+ public static LDAPUser getUserFromLDAP(HttpServletRequest req) throws Exception {
+ KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) req.getUserPrincipal();
+ AccessToken accessToken = keycloakPrincipal.getKeycloakSecurityContext().getToken();
+ String username = accessToken.getPreferredUsername();
+
+ // Retrieve kerberos credential from accessToken and deserialize it
+ String serializedGssCredential = (String) accessToken.getOtherClaims().get(KerberosConstants.GSS_DELEGATION_CREDENTIAL);
+ GSSCredential deserializedGssCredential = KerberosSerializationUtils.deserializeCredential(serializedGssCredential);
+
+ // First try to invoke without gssCredential. It should fail. This is here just for illustration purposes
+ try {
+ invokeLdap(null, username);
+ throw new RuntimeException("Not expected to authenticate to LDAP without credential");
+ } catch (NamingException nse) {
+ System.out.println("GSSCredentialsClient: Expected exception: " + nse.getMessage());
+ }
+
+ return invokeLdap(deserializedGssCredential, username);
+ }
+
+ private static LDAPUser invokeLdap(GSSCredential gssCredential, String username) throws NamingException {
+ Hashtable env = new Hashtable(11);
+ env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+ env.put(Context.PROVIDER_URL, "ldap://localhost:10389");
+
+ if (gssCredential != null) {
+ env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+ env.put(Sasl.CREDENTIALS, gssCredential);
+ }
+
+ DirContext ctx = new InitialDirContext(env);
+ try {
+ Attributes attrs = ctx.getAttributes("uid=" + username + ",ou=People,dc=keycloak,dc=org");
+ String uid = username;
+ String cn = (String) attrs.get("cn").get();
+ String sn = (String) attrs.get("sn").get();
+ return new LDAPUser(uid, cn, sn);
+ } finally {
+ ctx.close();
+ }
+ }
+
+ public static class LDAPUser {
+
+ private final String uid;
+ private final String cn;
+ private final String sn;
+
+ public LDAPUser(String uid, String cn, String sn) {
+ this.uid = uid;
+ this.cn = cn;
+ this.sn = sn;
+ }
+
+ public String getUid() {
+ return uid;
+ }
+
+ public String getCn() {
+ return cn;
+ }
+
+ public String getSn() {
+ return sn;
+ }
+ }
+}
diff --git a/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json b/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json
new file mode 100644
index 0000000000..dfb8577471
--- /dev/null
+++ b/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json
@@ -0,0 +1,11 @@
+{
+ "realm" : "kerberos-demo",
+ "resource" : "kerberos-app",
+ "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url": "/auth",
+ "ssl-required" : "external",
+ "enable-basic-auth" : "true",
+ "credentials": {
+ "secret": "password"
+ }
+}
\ No newline at end of file
diff --git a/examples/kerberos/src/main/webapp/WEB-INF/web.xml b/examples/kerberos/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000..aa2116608a
--- /dev/null
+++ b/examples/kerberos/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,37 @@
+
+
+
+ kerberos-portal
+
+
+
+ KerberosApp
+ /*
+
+
+ user
+
+
+
+
+
+
+ KEYCLOAK
+ does-not-matter
+
+
+
+ user
+
+
\ No newline at end of file
diff --git a/examples/kerberos/src/main/webapp/index.jsp b/examples/kerberos/src/main/webapp/index.jsp
new file mode 100644
index 0000000000..c1df8f0878
--- /dev/null
+++ b/examples/kerberos/src/main/webapp/index.jsp
@@ -0,0 +1,37 @@
+<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
+ pageEncoding="ISO-8859-1" %>
+<%@ page import="org.keycloak.constants.ServiceUrlConstants" %>
+<%@ page import="org.keycloak.util.KeycloakUriBuilder" %>
+<%@ page import="org.keycloak.example.kerberos.GSSCredentialsClient" %>
+<%@ page import="org.keycloak.example.kerberos.GSSCredentialsClient.LDAPUser" %>
+<%@ page session="false" %>
+
+
+
+
+ Kerberos Credentials Delegation Example
+
+
+