diff --git a/examples/kerberos/README.md b/examples/kerberos/README.md
new file mode 100644
index 00000000000..5acdb624ba8
--- /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 00000000000..0e7fd96fa73
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 00000000000..0f044f6ae9a
--- /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 00000000000..431876e35e5
--- /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.source}
+ ${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 00000000000..19fbf910c08
--- /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, krb5Oid, deserializedGssCredFromKeycloakAccessToken, GSSContext.DEFAULT_LIFETIME);
+ *
+ * In this example we will authenticate with GSSCredential against LDAP server, which calls GSS API under the hood
+ *
+ * @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) keycloakPrincipal.getKeycloakSecurityContext().getToken().getOtherClaims().get(KerberosConstants.GSS_DELEGATION_CREDENTIAL);
+ GSSCredential gssCredential = KerberosSerializationUtils.deserializeCredential(serializedGssCredential);
+
+ // First try to invoke without gssCredential. It should fail
+ 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(gssCredential, 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 00000000000..dfb8577471d
--- /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 00000000000..aa2116608a3
--- /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 00000000000..933b1d5f571
--- /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
+
+
+ Kerberos Credentials Delegation Example
+
+
+<%
+ String logoutUri = KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
+ .queryParam("redirect_uri", "/kerberos-portal").build("kerberos-demo").toString();
+%>
+ List of users from LDAP | Logout
+
+<%
+ try {
+ GSSCredentialsClient.LDAPUser ldapUser = GSSCredentialsClient.getUserFromLDAP(request);
+ out.println("uid: " + ldapUser.getUid() + "
");
+ out.println("cn: " + ldapUser.getCn() + "
");
+ out.println("sn: " + ldapUser.getSn() + "
");
+ } catch (Exception e) {
+ e.printStackTrace();
+ out.println("There was a failure invoking LDAP. Check server.log for more details");
+ }
+%>
+
+
\ No newline at end of file
diff --git a/examples/pom.xml b/examples/pom.xml
index 9a8a5853ba4..eb68e338648 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -33,5 +33,6 @@
multi-tenant
basic-auth
fuse
+ kerberos
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
index 04b498e48f6..f98364ad6b0 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
@@ -3,7 +3,7 @@ package org.keycloak.federation.kerberos;
import java.util.Map;
import org.keycloak.models.UserFederationProviderModel;
-import org.keycloak.models.KerberosConstants;
+import org.keycloak.constants.KerberosConstants;
/**
* Common configuration useful for all providers
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java
index c1f59b76ec8..32349b78293 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosConfig.java
@@ -1,6 +1,6 @@
package org.keycloak.federation.kerberos;
-import org.keycloak.models.KerberosConstants;
+import org.keycloak.constants.KerberosConstants;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
index 603f3bd950f..40094ef5aa2 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
@@ -20,7 +20,7 @@ import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
-import org.keycloak.models.KerberosConstants;
+import org.keycloak.constants.KerberosConstants;
/**
* @author Marek Posolda
@@ -176,19 +176,21 @@ public class KerberosFederationProvider implements UserFederationProvider {
spnegoAuthenticator.authenticate();
+ Map state = new HashMap();
if (spnegoAuthenticator.isAuthenticated()) {
- Map state = new HashMap();
- state.put(KerberosConstants.GSS_DELEGATION_CREDENTIAL, spnegoAuthenticator.getDelegationCredential());
-
String username = spnegoAuthenticator.getAuthenticatedUsername();
UserModel user = findOrCreateAuthenticatedUser(realm, username);
if (user == null) {
return CredentialValidationOutput.failed();
} else {
+ String delegationCredential = spnegoAuthenticator.getSerializedDelegationCredential();
+ if (delegationCredential != null) {
+ state.put(KerberosConstants.GSS_DELEGATION_CREDENTIAL, delegationCredential);
+ }
+
return new CredentialValidationOutput(user, CredentialValidationOutput.Status.AUTHENTICATED, state);
}
} else {
- Map state = new HashMap();
state.put(KerberosConstants.RESPONSE_TOKEN, spnegoAuthenticator.getResponseToken());
return new CredentialValidationOutput(null, CredentialValidationOutput.Status.CONTINUE, state);
}
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
index ddebe667bca..fa136220c65 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
@@ -105,7 +105,7 @@ public class KerberosUsernamePasswordAuthenticator {
try {
loginContext.logout();
} catch (LoginException le) {
- logger.error("Failed to logout kerberos server subject: " + config.getServerPrincipal(), le);
+ logger.error("Failed to logout kerberos subject", le);
}
}
}
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java
index 1132351b17c..6dd2576448c 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java
@@ -1,7 +1,6 @@
package org.keycloak.federation.kerberos.impl;
import java.io.IOException;
-import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import javax.security.auth.Subject;
@@ -13,6 +12,7 @@ import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.jboss.logging.Logger;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
+import org.keycloak.util.KerberosSerializationUtils;
/**
* @author Marek Posolda
@@ -21,8 +21,6 @@ public class SPNEGOAuthenticator {
private static final Logger log = Logger.getLogger(SPNEGOAuthenticator.class);
- private static final GSSManager GSS_MANAGER = GSSManager.getInstance();
-
private final KerberosServerSubjectAuthenticator kerberosSubjectAuthenticator;
private final String spnegoToken;
private final CommonKerberosConfig kerberosConfig;
@@ -61,8 +59,24 @@ public class SPNEGOAuthenticator {
return responseToken;
}
- public GSSCredential getDelegationCredential() {
- return delegationCredential;
+ public String getSerializedDelegationCredential() {
+ if (delegationCredential == null) {
+ if (log.isTraceEnabled()) {
+ log.trace("No delegation credential available.");
+ }
+
+ return null;
+ }
+
+ try {
+ if (log.isTraceEnabled()) {
+ log.trace("Serializing credential " + delegationCredential);
+ }
+ return KerberosSerializationUtils.serializeCredential(delegationCredential);
+ } catch (KerberosSerializationUtils.KerberosSerializationException kse) {
+ log.warn("Couldn't serialize credential: " + delegationCredential, kse);
+ return null;
+ }
}
/**
@@ -114,7 +128,8 @@ public class SPNEGOAuthenticator {
protected GSSContext establishContext() throws GSSException, IOException {
- GSSContext gssContext = GSS_MANAGER.createContext((GSSCredential) null);
+ GSSManager manager = GSSManager.getInstance();
+ GSSContext gssContext = manager.createContext((GSSCredential) null);
byte[] inputToken = Base64.decode(spnegoToken);
byte[] respToken = gssContext.acceptSecContext(inputToken, 0, inputToken.length);
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 7aabe268114..f48880cfc9f 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -15,7 +15,7 @@ import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
-import org.keycloak.models.KerberosConstants;
+import org.keycloak.constants.KerberosConstants;
import org.picketlink.idm.IdentityManagementException;
import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.PartitionManager;
@@ -344,9 +344,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
spnegoAuthenticator.authenticate();
+ Map state = new HashMap();
if (spnegoAuthenticator.isAuthenticated()) {
- Map state = new HashMap();
- state.put(KerberosConstants.GSS_DELEGATION_CREDENTIAL, spnegoAuthenticator.getDelegationCredential());
// TODO: This assumes that LDAP "uid" is equal to kerberos principal name. Like uid "hnelson" and kerberos principal "hnelson@KEYCLOAK.ORG".
// Check if it's correct or if LDAP attribute for mapping kerberos principal should be available (For ApacheDS it seems to be attribute "krb5PrincipalName" but on MSAD it's likely different)
@@ -356,11 +355,15 @@ public class LDAPFederationProvider implements UserFederationProvider {
if (user == null) {
logger.warn("Kerberos/SPNEGO authentication succeeded with username [" + username + "], but couldn't find or create user with federation provider [" + model.getDisplayName() + "]");
return CredentialValidationOutput.failed();
- }
+ } else {
+ String delegationCredential = spnegoAuthenticator.getSerializedDelegationCredential();
+ if (delegationCredential != null) {
+ state.put(KerberosConstants.GSS_DELEGATION_CREDENTIAL, delegationCredential);
+ }
- return new CredentialValidationOutput(user, CredentialValidationOutput.Status.AUTHENTICATED, state);
+ return new CredentialValidationOutput(user, CredentialValidationOutput.Status.AUTHENTICATED, state);
+ }
} else {
- Map state = new HashMap();
state.put(KerberosConstants.RESPONSE_TOKEN, spnegoAuthenticator.getResponseToken());
return new CredentialValidationOutput(null, CredentialValidationOutput.Status.CONTINUE, state);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java
index 4363ce927c9..d4d5c977753 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/kerberos/LDAPProviderKerberosConfig.java
@@ -1,7 +1,7 @@
package org.keycloak.federation.ldap.kerberos;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
-import org.keycloak.models.KerberosConstants;
+import org.keycloak.constants.KerberosConstants;
import org.keycloak.models.UserFederationProviderModel;
/**
diff --git a/model/api/src/main/java/org/keycloak/models/CredentialValidationOutput.java b/model/api/src/main/java/org/keycloak/models/CredentialValidationOutput.java
index 70e4093ab53..a056ef9ec62 100644
--- a/model/api/src/main/java/org/keycloak/models/CredentialValidationOutput.java
+++ b/model/api/src/main/java/org/keycloak/models/CredentialValidationOutput.java
@@ -12,16 +12,16 @@ public class CredentialValidationOutput {
private final UserModel authenticatedUser; // authenticated user.
private final Status authStatus; // status whether user is authenticated or more steps needed
- private final Map state; // Additional state related to authentication. It can contain data to be sent back to client or data about used credentials.
+ private final Map state; // Additional state related to authentication. It can contain data to be sent back to client or data about used credentials.
- public CredentialValidationOutput(UserModel authenticatedUser, Status authStatus, Map state) {
+ public CredentialValidationOutput(UserModel authenticatedUser, Status authStatus, Map state) {
this.authenticatedUser = authenticatedUser;
this.authStatus = authStatus;
this.state = state;
}
public static CredentialValidationOutput failed() {
- return new CredentialValidationOutput(null, CredentialValidationOutput.Status.FAILED, new HashMap());
+ return new CredentialValidationOutput(null, CredentialValidationOutput.Status.FAILED, new HashMap());
}
public UserModel getAuthenticatedUser() {
@@ -32,7 +32,7 @@ public class CredentialValidationOutput {
return authStatus;
}
- public Map getState() {
+ public Map getState() {
return state;
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index 92465e1620b..f52e9ef39a9 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -302,6 +302,7 @@ public abstract class ClientAdapter extends A
mapping.setId(entity.getId());
mapping.setName(entity.getName());
mapping.setProtocol(entity.getProtocol());
+ mapping.setProtocolMapper(entity.getProtocolMapper());
mapping.setConsentRequired(entity.isConsentRequired());
mapping.setConsentText(entity.getConsentText());
Map config = new HashMap();
@@ -309,6 +310,7 @@ public abstract class ClientAdapter extends A
config.putAll(entity.getConfig());
}
mapping.setConfig(config);
+ result.add(mapping);
}
return result;
}
diff --git a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java
index abb915ee5ec..a00932380d1 100755
--- a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java
@@ -10,10 +10,13 @@ import java.lang.reflect.Method;
*/
public class ProtocolMapperUtils {
public static final String USER_ATTRIBUTE = "user.attribute";
+ public static final String USER_SESSION_NOTE = "user.session.note";
public static final String USER_MODEL_PROPERTY_LABEL = "User Property";
public static final String USER_MODEL_PROPERTY_HELP_TEXT = "Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method.";
public static final String USER_MODEL_ATTRIBUTE_LABEL = "User Attribute";
public static final String USER_MODEL_ATTRIBUTE_HELP_TEXT = "Name of stored user attribute which is the name of an attribute within the UserModel.attribute map.";
+ public static final String USER_SESSION_MODEL_NOTE_LABEL = "User Session Note";
+ public static final String USER_SESSION_MODEL_NOTE_HELP_TEXT = "Name of stored user session note within the UserSessionModel.note map.";
public static String getUserModelValue(UserModel user, String propertyName) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
index 1fc1277fb1b..1c270333834 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
@@ -1,5 +1,6 @@
package org.keycloak.protocol.oidc;
+import org.keycloak.constants.KerberosConstants;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
@@ -11,6 +12,7 @@ import org.keycloak.protocol.oidc.mappers.OIDCAddressMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.protocol.oidc.mappers.OIDCFullNameMapper;
import org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper;
+import org.keycloak.protocol.oidc.mappers.OIDCUserSessionNoteMapper;
import org.keycloak.services.managers.AuthenticationManager;
import java.util.ArrayList;
@@ -89,6 +91,13 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
ProtocolMapperModel address = OIDCAddressMapper.createAddressMapper();
builtins.add(address);
+
+ model = OIDCUserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
+ KerberosConstants.GSS_DELEGATION_CREDENTIAL,
+ KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
+ true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
+ true, false);
+ builtins.add(model);
}
@Override
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java
new file mode 100644
index 00000000000..effa69af83a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java
@@ -0,0 +1,134 @@
+package org.keycloak.protocol.oidc.mappers;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
+
+/**
+ * Mappings UserSessionModel.note to an ID Token claim.
+ *
+ * @author Marek Posolda
+ */
+public class OIDCUserSessionNoteMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
+
+ private static final List configProperties = new ArrayList();
+
+ static {
+ ConfigProperty property;
+ property = new ConfigProperty();
+ property.setName(ProtocolMapperUtils.USER_SESSION_NOTE);
+ property.setLabel(ProtocolMapperUtils.USER_SESSION_MODEL_NOTE_LABEL);
+ property.setHelpText(ProtocolMapperUtils.USER_SESSION_MODEL_NOTE_HELP_TEXT);
+ property.setType(ConfigProperty.STRING_TYPE);
+ configProperties.add(property);
+ property = new ConfigProperty();
+ property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
+ property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
+ property.setType(ConfigProperty.STRING_TYPE);
+ property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created.");
+ configProperties.add(property);
+ property = new ConfigProperty();
+ property.setName(OIDCAttributeMapperHelper.JSON_TYPE);
+ property.setLabel(OIDCAttributeMapperHelper.JSON_TYPE);
+ property.setType(ConfigProperty.STRING_TYPE);
+ property.setDefaultValue(ConfigProperty.STRING_TYPE);
+ property.setHelpText("JSON type that should be used to populate the json claim in the token. long, int, boolean, and String are valid values.");
+ configProperties.add(property);
+ property = new ConfigProperty();
+ property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
+ property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
+ property.setType(ConfigProperty.BOOLEAN_TYPE);
+ property.setDefaultValue("true");
+ property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
+ configProperties.add(property);
+ property = new ConfigProperty();
+ property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
+ property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
+ property.setType(ConfigProperty.BOOLEAN_TYPE);
+ property.setDefaultValue("true");
+ property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
+ configProperties.add(property);
+
+ }
+
+ public static final String PROVIDER_ID = "oidc-usersessionmodel-note-mapper";
+
+
+ public List getConfigProperties() {
+ return configProperties;
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "User Session Note";
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return TOKEN_MAPPER_CATEGORY;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Map a custom user session note to a token claim.";
+ }
+
+ @Override
+ public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
+ UserSessionModel userSession, ClientSessionModel clientSession) {
+ if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
+
+ setClaim(token, mappingModel, userSession);
+ return token;
+ }
+
+ protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
+ String noteName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_SESSION_NOTE);
+ String noteValue = userSession.getNote(noteName);
+ if (noteValue == null) return;
+ OIDCAttributeMapperHelper.mapClaim(token, mappingModel, noteValue);
+ }
+
+ @Override
+ public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
+ setClaim(token, mappingModel, userSession);
+ return token;
+ }
+
+ public static ProtocolMapperModel createClaimMapper(String name,
+ String userSessionNote,
+ String tokenClaimName, String jsonType,
+ boolean consentRequired, String consentText,
+ boolean accessToken, boolean idToken) {
+ ProtocolMapperModel mapper = new ProtocolMapperModel();
+ mapper.setName(name);
+ mapper.setProtocolMapper(PROVIDER_ID);
+ mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+ mapper.setConsentRequired(consentRequired);
+ mapper.setConsentText(consentText);
+ Map config = new HashMap();
+ config.put(ProtocolMapperUtils.USER_SESSION_NOTE, userSessionNote);
+ config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, tokenClaimName);
+ config.put(OIDCAttributeMapperHelper.JSON_TYPE, jsonType);
+ if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
+ if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
+ mapper.setConfig(config);
+ return mapper;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
index 9791de321d3..7dca9c14822 100644
--- a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
@@ -1,5 +1,7 @@
package org.keycloak.services.managers;
+import java.util.Map;
+
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@@ -19,7 +21,7 @@ import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.KerberosConstants;
+import org.keycloak.constants.KerberosConstants;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.messages.Messages;
@@ -94,7 +96,7 @@ public class HttpAuthenticationManager {
CredentialValidationOutput output = session.users().validCredentials(realm, spnegoCredential);
if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) {
- return sendResponse(output.getAuthenticatedUser(), "spnego");
+ return sendResponse(output.getAuthenticatedUser(), output.getState(), "spnego");
} else {
String spnegoResponseToken = (String) output.getState().get(KerberosConstants.RESPONSE_TOKEN);
return challengeNegotiation(spnegoResponseToken);
@@ -104,7 +106,7 @@ public class HttpAuthenticationManager {
// Send response after successful authentication
- private HttpAuthOutput sendResponse(UserModel user, String authMethod) {
+ private HttpAuthOutput sendResponse(UserModel user, Map authState, String authMethod) {
if (logger.isTraceEnabled()) {
logger.trace("User " + user.getUsername() + " authenticated with " + authMethod);
}
@@ -115,6 +117,12 @@ public class HttpAuthenticationManager {
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, Messages.ACCOUNT_DISABLED);
} else {
UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false);
+
+ // Propagate state (like kerberos delegation credentials etc) as attributes of userSession
+ for (Map.Entry entry : authState.entrySet()) {
+ userSession.setNote(entry.getKey(), entry.getValue());
+ }
+
TokenManager.attachClientSession(userSession, clientSession);
event.user(user)
.session(userSession)
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
index e4fff5ff4ac..717a1168a37 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
@@ -4,23 +4,11 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.KerberosConstants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RequiredCredentialModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserFederationProvider;
-import org.keycloak.models.UserFederationProviderFactory;
-import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
-import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
-import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
-import org.keycloak.representations.idm.UserFederationProviderRepresentation;
-import org.keycloak.services.managers.UsersSyncManager;
-import org.keycloak.timer.TimerProvider;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -30,14 +18,12 @@ import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-import java.util.HashMap;
+
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
/**
* Base resource for managing users
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
index 04e3c7f9f4b..47f1b65faa4 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
@@ -10,7 +10,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
-import org.keycloak.models.KerberosConstants;
+import org.keycloak.constants.KerberosConstants;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
index 1615ecc7632..137b29a55a3 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
+++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
@@ -2,5 +2,6 @@ org.keycloak.protocol.oidc.mappers.OIDCUserAttributeMapper
org.keycloak.protocol.oidc.mappers.OIDCFullNameMapper
org.keycloak.protocol.oidc.mappers.OIDCUserModelMapper
org.keycloak.protocol.oidc.mappers.OIDCAddressMapper
+org.keycloak.protocol.oidc.mappers.OIDCUserSessionNoteMapper
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPConfiguration.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPConfiguration.java
index 63445b2a398..94b767233da 100644
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPConfiguration.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPConfiguration.java
@@ -8,7 +8,7 @@ import java.util.Map;
import java.util.Properties;
import org.jboss.logging.Logger;
-import org.keycloak.models.KerberosConstants;
+import org.keycloak.constants.KerberosConstants;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.UserFederationProvider;
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java
index 1bf4f80df2c..4fde15bd3e7 100644
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java
@@ -121,6 +121,7 @@ public class LDAPEmbeddedServer {
LdapServer ldapServer = new LdapServer();
ldapServer.setServiceName("DefaultLdapServer");
+ ldapServer.setSearchBaseDn(this.baseDN);
// Read the transports
Transport ldap = new TcpTransport(this.bindHost, this.bindPort, 3, 50);
diff --git a/testsuite/integration/src/main/resources/kerberos/http.keytab b/testsuite/integration/src/main/resources/kerberos/http.keytab
index c156500feef..0e7fd96fa73 100644
Binary files a/testsuite/integration/src/main/resources/kerberos/http.keytab and b/testsuite/integration/src/main/resources/kerberos/http.keytab differ
diff --git a/testsuite/integration/src/main/resources/kerberos/test-krb5.conf b/testsuite/integration/src/main/resources/kerberos/test-krb5.conf
index 350c086af73..a775b47c86c 100644
--- a/testsuite/integration/src/main/resources/kerberos/test-krb5.conf
+++ b/testsuite/integration/src/main/resources/kerberos/test-krb5.conf
@@ -1,13 +1,14 @@
[libdefaults]
default_realm = KEYCLOAK.ORG
- default_tgs_enctypes = des3-cbc-sha1-kd rc4-hmac
- default_tkt_enctypes = des3-cbc-sha1-kd rc4-hmac
- permitted_enctypes = des3-cbc-sha1-kd rc4-hmac
+ default_tgs_enctypes = des3-cbc-sha1-kd aes256-cts-hmac-sha1-96 rc4-hmac
+ default_tkt_enctypes = des3-cbc-sha1-kd aes256-cts-hmac-sha1-96 rc4-hmac
+ permitted_enctypes = des3-cbc-sha1-kd aes256-cts-hmac-sha1-96 rc4-hmac
kdc_timeout = 30000
dns_lookup_realm = false
dns_lookup_kdc = false
dns_canonicalize_hostname = false
ignore_acceptor_hostname = true
+ forwardable = true
[realms]
KEYCLOAK.ORG = {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
index 0bb3cd25870..2cbe7aeed26 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
@@ -16,31 +16,33 @@ import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.FixMethodOrder;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.runners.MethodSorters;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.events.Details;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
-import org.keycloak.federation.kerberos.KerberosConfig;
-import org.keycloak.models.KerberosConstants;
+import org.keycloak.constants.KerberosConstants;
+import org.keycloak.models.ApplicationModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
+import org.keycloak.protocol.oidc.mappers.OIDCUserSessionNoteMapper;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.adapter.AdapterTest;
+import org.keycloak.testsuite.adapter.AdapterTestStrategy;
import org.keycloak.testsuite.pages.AccountPasswordPage;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
-import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
/**
@@ -48,6 +50,8 @@ import org.openqa.selenium.WebDriver;
*/
public abstract class AbstractKerberosTest {
+ protected String KERBEROS_APP_URL = "http://localhost:8081/kerberos-portal";
+
protected KeycloakSPNegoSchemeFactory spnegoSchemeFactory;
protected ResteasyClient client;
@@ -63,9 +67,6 @@ public abstract class AbstractKerberosTest {
@WebResource
protected AccountPasswordPage changePasswordPage;
- @WebResource
- protected AppPage appPage;
-
protected abstract CommonKerberosConfig getKerberosConfig();
protected abstract KeycloakRule getKeycloakRule();
protected abstract AssertEvents getAssertEvents();
@@ -88,7 +89,11 @@ public abstract class AbstractKerberosTest {
@Test
public void spnegoNotAvailableTest() throws Exception {
initHttpClient(false);
- Response response = client.target(oauth.getLoginFormUrl()).request().get();
+
+ driver.navigate().to(KERBEROS_APP_URL);
+ String kcLoginPageLocation = driver.getCurrentUrl();
+
+ Response response = client.target(kcLoginPageLocation).request().get();
Assert.assertEquals(401, response.getStatus());
Assert.assertEquals(KerberosConstants.NEGOTIATE, response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE));
String responseText = response.readEntity(String.class);
@@ -105,17 +110,21 @@ public abstract class AbstractKerberosTest {
Assert.assertEquals(302, spnegoResponse.getStatus());
events.expectLogin()
+ .client("kerberos-app")
.user(keycloakRule.getUser("test", "hnelson").getId())
+ .detail(Details.REDIRECT_URI, KERBEROS_APP_URL)
.detail(Details.AUTH_METHOD, "spnego")
.detail(Details.USERNAME, "hnelson")
.assertEvent();
String location = spnegoResponse.getLocation().toString();
driver.navigate().to(location);
- Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
- Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+ String pageSource = driver.getPageSource();
+ Assert.assertTrue(pageSource.contains("Kerberos Test") && pageSource.contains("Kerberos servlet secured content"));
spnegoResponse.close();
+ events.clear();
}
@@ -157,18 +166,72 @@ public abstract class AbstractKerberosTest {
Response spnegoResponse = spnegoLogin("jduke", "theduke");
Assert.assertEquals(302, spnegoResponse.getStatus());
events.expectLogin()
+ .client("kerberos-app")
.user(keycloakRule.getUser("test", "jduke").getId())
+ .detail(Details.REDIRECT_URI, KERBEROS_APP_URL)
.detail(Details.AUTH_METHOD, "spnego")
.detail(Details.USERNAME, "jduke")
.assertEvent();
spnegoResponse.close();
}
+ @Test
+ public void credentialDelegationTest() throws Exception {
+ // Add kerberos delegation credential mapper
+ getKeycloakRule().update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ ProtocolMapperModel protocolMapper = OIDCUserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
+ KerberosConstants.GSS_DELEGATION_CREDENTIAL,
+ KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
+ true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
+ true, false);
+
+ ApplicationModel kerberosApp = appRealm.getApplicationByName("kerberos-app");
+ kerberosApp.addProtocolMapper(protocolMapper);
+ }
+
+ });
+
+ // SPNEGO login
+ spnegoLoginTestImpl();
+
+ // Assert servlet authenticated to LDAP with delegated credential
+ driver.navigate().to(KERBEROS_APP_URL + KerberosCredDelegServlet.CRED_DELEG_TEST_PATH);
+ String pageSource = driver.getPageSource();
+ Assert.assertTrue(pageSource.contains("LDAP Data: Horatio Nelson"));
+
+ // Remove kerberos delegation credential mapper
+ getKeycloakRule().update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ ApplicationModel kerberosApp = appRealm.getApplicationByName("kerberos-app");
+ ProtocolMapperModel toRemove = kerberosApp.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME);
+ kerberosApp.removeProtocolMapper(toRemove);
+ }
+
+ });
+
+ // Clear driver and login again. I can't invoke LDAP now as GSS Credential is not in accessToken
+ driver.manage().deleteAllCookies();
+ spnegoLoginTestImpl();
+ driver.navigate().to(KERBEROS_APP_URL + KerberosCredDelegServlet.CRED_DELEG_TEST_PATH);
+ pageSource = driver.getPageSource();
+ Assert.assertFalse(pageSource.contains("LDAP Data: Horatio Nelson"));
+ Assert.assertTrue(pageSource.contains("LDAP Data: ERROR"));
+ }
+
protected Response spnegoLogin(String username, String password) {
+ driver.navigate().to(KERBEROS_APP_URL);
+ String kcLoginPageLocation = driver.getCurrentUrl();
+
+ // Request for SPNEGO login sent with Resteasy client
spnegoSchemeFactory.setCredentials(username, password);
- return client.target(oauth.getLoginFormUrl()).request().get();
+ return client.target(kcLoginPageLocation).request().get();
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosCredDelegServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosCredDelegServlet.java
new file mode 100644
index 00000000000..22baba480d2
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosCredDelegServlet.java
@@ -0,0 +1,96 @@
+package org.keycloak.testsuite.federation;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+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.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.ietf.jgss.GSSCredential;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.constants.KerberosConstants;
+import org.keycloak.util.KerberosSerializationUtils;
+
+/**
+ * @author Marek Posolda
+ */
+public class KerberosCredDelegServlet extends HttpServlet {
+
+ public static final String CRED_DELEG_TEST_PATH = "/cred-deleg-test";
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ String ldapData = null;
+
+ if (req.getRequestURI().endsWith(CRED_DELEG_TEST_PATH)) {
+
+ try {
+ // Retrieve kerberos credential from accessToken and deserialize it
+ KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) req.getUserPrincipal();
+ String serializedGssCredential = (String) keycloakPrincipal.getKeycloakSecurityContext().getToken().getOtherClaims().get(KerberosConstants.GSS_DELEGATION_CREDENTIAL);
+ GSSCredential gssCredential = KerberosSerializationUtils.deserializeCredential(serializedGssCredential);
+
+ // First try to invoke without gssCredential. It should fail
+ try {
+ invokeLdap(null);
+ throw new RuntimeException("Not expected to authenticate to LDAP without credential");
+ } catch (NamingException nse) {
+ System.out.println("Expected exception: " + nse.getMessage());
+ }
+
+ ldapData = invokeLdap(gssCredential);
+ } catch (KerberosSerializationUtils.KerberosSerializationException kse) {
+ System.err.println("KerberosSerializationUtils.KerberosSerializationException: " + kse.getMessage());
+ ldapData = "ERROR";
+ } catch (Exception e) {
+ e.printStackTrace();
+ resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ resp.setContentType("text/html");
+ PrintWriter pw = resp.getWriter();
+ pw.printf("%s", "Kerberos Test");
+ pw.printf("Kerberos servlet secured content
");
+
+ if (ldapData != null) {
+ pw.printf("LDAP Data: " + ldapData + "
");
+ }
+
+ pw.print("");
+ pw.flush();
+
+
+ }
+
+ private String invokeLdap(GSSCredential gssCredential) 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=hnelson,ou=People,dc=keycloak,dc=org");
+ String cn = (String) attrs.get("cn").get();
+ String sn = (String) attrs.get("sn").get();
+ return cn + " " + sn;
+ } finally {
+ ctx.close();
+ }
+ }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java
index 4123508815d..07ca87cf9d3 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java
@@ -1,43 +1,29 @@
package org.keycloak.testsuite.federation;
+import java.net.URL;
import java.util.Map;
-import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.junit.Assert;
import org.junit.ClassRule;
-import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
-import org.junit.runners.MethodSorters;
-import org.keycloak.OAuth2Constants;
import org.keycloak.events.Details;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
-import org.keycloak.federation.kerberos.KerberosConfig;
-import org.keycloak.federation.kerberos.KerberosFederationProviderFactory;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
-import org.keycloak.models.KerberosConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
-import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.pages.AccountPasswordPage;
-import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
-import org.keycloak.testsuite.pages.AppPage;
-import org.keycloak.testsuite.pages.LoginPage;
-import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.rule.KerberosRule;
import org.keycloak.testsuite.rule.KeycloakRule;
-import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
-import org.openqa.selenium.WebDriver;
/**
* Test of LDAPFederationProvider (Kerberos backed by LDAP)
@@ -46,21 +32,30 @@ import org.openqa.selenium.WebDriver;
*/
public class KerberosLdapTest extends AbstractKerberosTest {
- public static final String CONFIG_LOCATION = "kerberos/kerberos-ldap-connection.properties";
+ private static final String PROVIDER_CONFIG_LOCATION = "kerberos/kerberos-ldap-connection.properties";
private static UserFederationProviderModel ldapModel = null;
- private static KerberosRule kerberosRule = new KerberosRule(CONFIG_LOCATION);
+ private static KerberosRule kerberosRule = new KerberosRule(PROVIDER_CONFIG_LOCATION);
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
+ keycloakRule.deployApplication("kerberos-portal", "/kerberos-portal", KerberosCredDelegServlet.class, url.getPath(), "user");
+
Map ldapConfig = kerberosRule.getConfig();
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "kerberos-ldap", -1, -1, 0);
- appRealm.addRequiredCredential(UserCredentialModel.KERBEROS);
}
- });
+ }) {
+
+ @Override
+ protected void importRealm() {
+ server.importRealm(getClass().getResourceAsStream("/kerberos-test/kerberosrealm.json"));
+ }
+
+ };
@ClassRule
public static TestRule chain = RuleChain
@@ -129,12 +124,15 @@ public class KerberosLdapTest extends AbstractKerberosTest {
Response spnegoResponse = spnegoLogin("jduke", "newPass");
Assert.assertEquals(302, spnegoResponse.getStatus());
events.expectLogin()
+ .client("kerberos-app")
.user(keycloakRule.getUser("test", "jduke").getId())
+ .detail(Details.REDIRECT_URI, KERBEROS_APP_URL)
.detail(Details.AUTH_METHOD, "spnego")
.detail(Details.USERNAME, "jduke")
.assertEvent();
// Change password back
+ changePasswordPage.open();
loginPage.login("jduke", "newPass");
changePasswordPage.assertCurrent();
changePasswordPage.changePassword("newPass", "theduke", "theduke");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java
index 65a753ea3df..c665f44c3c4 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosStandaloneTest.java
@@ -1,5 +1,6 @@
package org.keycloak.testsuite.federation;
+import java.net.URL;
import java.util.Map;
import javax.ws.rs.core.Response;
@@ -13,16 +14,20 @@ import org.junit.rules.TestRule;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
import org.keycloak.federation.kerberos.KerberosConfig;
import org.keycloak.federation.kerberos.KerberosFederationProviderFactory;
-import org.keycloak.models.KerberosConstants;
+import org.keycloak.constants.KerberosConstants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.adapter.CustomerServlet;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.KerberosRule;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.testutils.KeycloakServer;
/**
* Test of KerberosFederationProvider (Kerberos not backed by LDAP)
@@ -31,22 +36,31 @@ import org.keycloak.testsuite.rule.WebRule;
*/
public class KerberosStandaloneTest extends AbstractKerberosTest {
- public static final String CONFIG_LOCATION = "kerberos/kerberos-standalone-connection.properties";
+ private static final String PROVIDER_CONFIG_LOCATION = "kerberos/kerberos-standalone-connection.properties";
private static UserFederationProviderModel kerberosModel;
- private static KerberosRule kerberosRule = new KerberosRule(CONFIG_LOCATION);
+ private static KerberosRule kerberosRule = new KerberosRule(PROVIDER_CONFIG_LOCATION);
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
- Map kerberosConfig = kerberosRule.getConfig();
+ URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
+ keycloakRule.deployApplication("kerberos-portal", "/kerberos-portal", KerberosCredDelegServlet.class, url.getPath(), "user");
+ Map kerberosConfig = kerberosRule.getConfig();
kerberosModel = appRealm.addUserFederationProvider(KerberosFederationProviderFactory.PROVIDER_NAME, kerberosConfig, 0, "kerberos-standalone", -1, -1, 0);
- appRealm.addRequiredCredential(UserCredentialModel.KERBEROS);
}
- });
+
+ }) {
+
+ @Override
+ protected void importRealm() {
+ server.importRealm(getClass().getResourceAsStream("/kerberos-test/kerberosrealm.json"));
+ }
+
+ };
diff --git a/testsuite/integration/src/test/resources/kerberos-test/kerberos-app-keycloak.json b/testsuite/integration/src/test/resources/kerberos-test/kerberos-app-keycloak.json
new file mode 100644
index 00000000000..609bcdb1aac
--- /dev/null
+++ b/testsuite/integration/src/test/resources/kerberos-test/kerberos-app-keycloak.json
@@ -0,0 +1,10 @@
+{
+ "realm": "test",
+ "resource": "kerberos-app",
+ "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url": "http://localhost:8081/auth",
+ "ssl-required" : "external",
+ "credentials": {
+ "secret": "password"
+ }
+}
diff --git a/testsuite/integration/src/test/resources/kerberos-test/kerberosrealm.json b/testsuite/integration/src/test/resources/kerberos-test/kerberosrealm.json
new file mode 100644
index 00000000000..b0fb9035913
--- /dev/null
+++ b/testsuite/integration/src/test/resources/kerberos-test/kerberosrealm.json
@@ -0,0 +1,54 @@
+{
+ "id": "test",
+ "realm": "test",
+ "enabled": true,
+ "sslRequired": "external",
+ "registrationAllowed": true,
+ "resetPasswordAllowed": true,
+ "passwordCredentialGrantAllowed": true,
+ "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" ],
+ "users" : [
+ {
+ "username" : "test-user@localhost",
+ "enabled": true,
+ "email" : "test-user@localhost",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "realmRoles": ["user"],
+ "applicationRoles": {
+ "account": [ "view-profile", "manage-account" ]
+ }
+ }
+ ],
+ "scopeMappings": [
+ {
+ "client": "kerberos-app",
+ "roles": ["user"]
+ }
+ ],
+ "applications": [
+ {
+ "name": "kerberos-app",
+ "enabled": true,
+ "baseUrl": "http://localhost:8081/kerberos-portal",
+ "redirectUris": [
+ "http://localhost:8081/kerberos-portal/*"
+ ],
+ "adminUrl": "http://localhost:8081/kerberos-portal/logout",
+ "secret": "password"
+ }
+ ],
+ "roles" : {
+ "realm" : [
+ {
+ "name": "user",
+ "description": "Have User privileges"
+ }
+ ]
+ }
+}
\ No newline at end of file