diff --git a/spring-security-kerberos-client/src/test/java/org/springframework/security/kerberos/client/TestMultiTierAuthentication.java b/spring-security-kerberos-client/src/test/java/org/springframework/security/kerberos/client/TestMultiTierAuthentication.java
new file mode 100644
index 0000000..9c2c007
--- /dev/null
+++ b/spring-security-kerberos-client/src/test/java/org/springframework/security/kerberos/client/TestMultiTierAuthentication.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2009-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.kerberos.client;
+
+import org.junit.Test;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.kerberos.authentication.KerberosAuthenticationProvider;
+import org.springframework.security.kerberos.authentication.KerberosMultiTier;
+import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider;
+import org.springframework.security.kerberos.authentication.KerberosServiceRequestToken;
+import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosClient;
+import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator;
+import org.springframework.security.kerberos.test.KerberosSecurityTestcase;
+import org.springframework.security.kerberos.test.MiniKdc;
+
+import java.io.File;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Bogdan Mustiata
+ */
+public class TestMultiTierAuthentication extends KerberosSecurityTestcase {
+
+ public static final String REALM_NAME = "EXAMPLE.COM";
+
+ public static final String USER_LOGIN_NAME = "user1";
+ public static final String USER_FQDN_NAME = "user1@EXAMPLE.COM";
+ public static final String USER_PASSWORD = "secret";
+
+ public static final String WEB_TIER_SPN = "HTTP/webtier@EXAMPLE.COM";
+ public static final String WEB_TIER_USER_PASSWORD = "secret";
+
+ public static final String SERVICE_TIER_SPN = "HTTP/servicetier@EXAMPLE.COM";
+ public static final String SERVICE_TIER_USER_PASSWORD = "secret";
+
+ @Test
+ public void testServer() throws Exception {
+ MiniKdc kdc = getKdc();
+ File workDir = getWorkDir();
+
+ File webTierKeytabFile = new File(workDir, "webtier.keytab");
+ kdc.createKeyabFile(webTierKeytabFile, WEB_TIER_SPN, WEB_TIER_USER_PASSWORD);
+
+ File serviceTierKeytabFile = new File(workDir, "servicetier.keytab");
+ kdc.createKeyabFile(serviceTierKeytabFile, SERVICE_TIER_SPN, SERVICE_TIER_USER_PASSWORD);
+
+ //
+ // User logs in as user1/secret
+ //
+ KerberosAuthenticationProvider kerberosAuthProvider =
+ createUserPassAuthenticator(/* debug: */ true);
+
+ Authentication authentication = kerberosAuthProvider
+ .authenticate(new UsernamePasswordAuthenticationToken(USER_LOGIN_NAME, USER_PASSWORD));
+
+ assertEquals(USER_FQDN_NAME, authentication.getName());
+
+ //
+ // User creates a ticket for the HTTP/webtier@EXAMPLE.COM, using
+ // and then calls the service, using the tokenData
+ //
+ authentication = KerberosMultiTier.authenticateService(
+ authentication, USER_LOGIN_NAME, 3600, WEB_TIER_SPN);
+
+ byte[] tokenData = KerberosMultiTier
+ .getTokenForService(authentication, WEB_TIER_SPN);
+
+ assertNotNull(tokenData);
+ assertTrue(tokenData.length != 0);
+
+ //
+ // The service HTTP/webtier@EXAMPLE.COM authenticates via tokens.
+ //
+ KerberosServiceAuthenticationProvider webTierAuthenticatorProvider =
+ createServiceAuthenticator(
+ true,
+ WEB_TIER_SPN,
+ REALM_NAME,
+ webTierKeytabFile.getCanonicalPath()
+ );
+
+
+ //
+ // The service HTTP/webtier@EXAMPLE.COM authenticates the user1@EXAMPLE.COM
+ // using the previously stored token, then authenticates itself further as
+ // user1@EXAMPLE.COM to the HTTP/servicetier@EXAMPLE.COM.
+ //
+ Authentication webTierAuthentication = webTierAuthenticatorProvider
+ .authenticate(new KerberosServiceRequestToken(tokenData));
+
+ assertEquals(USER_FQDN_NAME, webTierAuthentication.getName());
+
+ webTierAuthentication = KerberosMultiTier.authenticateService(
+ webTierAuthentication, USER_FQDN_NAME, 3600, SERVICE_TIER_SPN);
+
+ byte[] workplaceTokenData = KerberosMultiTier.getTokenForService(
+ webTierAuthentication, SERVICE_TIER_SPN);
+
+ //
+ // The service HTTP/icr@EXAMPLE.COM authenticates via tokens.
+ //
+ webTierAuthenticatorProvider =
+ createServiceAuthenticator(
+ true,
+ SERVICE_TIER_SPN,
+ REALM_NAME,
+ serviceTierKeytabFile.getCanonicalPath()
+ );
+
+ //
+ // The service HTTP/servicetier@EXAMPLE.COM authenticates via the previously saved
+ // token, received from the HTTP/webtier@EXAMPLE.COM on behalf of user1@EXAMPLE.COM
+ //
+ Authentication serviceTierAuthentication = webTierAuthenticatorProvider
+ .authenticate(new KerberosServiceRequestToken(workplaceTokenData));
+
+ assertEquals(USER_FQDN_NAME, serviceTierAuthentication.getName());
+ }
+
+ /**
+ * Create a username/password authenticator.
+ * @return
+ */
+ private KerberosAuthenticationProvider createUserPassAuthenticator(boolean debug) {
+ KerberosAuthenticationProvider kerberosAuthenticationProvider =
+ new KerberosAuthenticationProvider();
+
+ SunJaasKerberosClient sunJaasKerberosClient = new SunJaasKerberosClient();
+
+ sunJaasKerberosClient.setDebug(debug);
+ sunJaasKerberosClient.setMultiTier(true);
+
+ kerberosAuthenticationProvider.setKerberosClient(sunJaasKerberosClient);
+ kerberosAuthenticationProvider.setUserDetailsService(userDetailsService());
+
+ return kerberosAuthenticationProvider;
+ }
+
+ private KerberosServiceAuthenticationProvider createServiceAuthenticator(boolean debug,
+ String serviceName,
+ String realmName,
+ String keytabFileLocation) throws Exception {
+ KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider =
+ new KerberosServiceAuthenticationProvider();
+
+ SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
+ ticketValidator.setDebug(debug);
+ ticketValidator.setServicePrincipal(serviceName);
+ ticketValidator.setRealmName(realmName);
+ ticketValidator.setKeyTabLocation(new FileSystemResource(keytabFileLocation));
+ ticketValidator.setMultiTier(true);
+
+ ticketValidator.afterPropertiesSet();
+
+ kerberosServiceAuthenticationProvider.setTicketValidator(ticketValidator);
+ kerberosServiceAuthenticationProvider.setUserDetailsService(userDetailsService());
+
+ return kerberosServiceAuthenticationProvider;
+ }
+
+ private UserDetailsService userDetailsService() {
+ return new UserDetailsService() {
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ return new User(username, "notUsed", true, true, true, true,
+ AuthorityUtils.createAuthorityList("ROLE_USER"));
+
+ }
+ };
+ }
+
+}
diff --git a/spring-security-kerberos-client/src/test/resources/minikdc-krb5.conf b/spring-security-kerberos-client/src/test/resources/minikdc-krb5.conf
index d118dd1..ed6c5d9 100644
--- a/spring-security-kerberos-client/src/test/resources/minikdc-krb5.conf
+++ b/spring-security-kerberos-client/src/test/resources/minikdc-krb5.conf
@@ -18,6 +18,7 @@
[libdefaults]
default_realm = {0}
udp_preference_limit = 1
+ forwardable = true
[realms]
{0} = '{'
diff --git a/spring-security-kerberos-client/src/test/resources/minikdc.ldiff b/spring-security-kerberos-client/src/test/resources/minikdc.ldiff
index 603ccb5..e7cc011 100644
--- a/spring-security-kerberos-client/src/test/resources/minikdc.ldiff
+++ b/spring-security-kerberos-client/src/test/resources/minikdc.ldiff
@@ -44,4 +44,43 @@ sn: Service
uid: ldap
userPassword: secret
krb5PrincipalName: ldap/${4}@${2}.${3}
-krb5KeyVersionNumber: 0
\ No newline at end of file
+krb5KeyVersionNumber: 0
+
+dn: uid=user1,ou=users,dc=${0},dc=${1}
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: user1
+sn: Service
+uid: user1
+userPassword: secret
+krb5PrincipalName: user1@${2}.${3}
+krb5KeyVersionNumber: 0
+
+dn: uid=webtier,ou=users,dc=${0},dc=${1}
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: webtier
+sn: Service
+uid: webtier
+userPassword: secret
+krb5PrincipalName: HTTP/webtier@${2}.${3}
+krb5KeyVersionNumber: 0
+
+dn: uid=servicetier,ou=users,dc=${0},dc=${1}
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: servicetier
+sn: Service
+uid: servicetier
+userPassword: secret
+krb5PrincipalName: HTTP/servicetier@${2}.${3}
+krb5KeyVersionNumber: 0
diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/JaasSubjectHolder.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/JaasSubjectHolder.java
new file mode 100644
index 0000000..7991ca9
--- /dev/null
+++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/JaasSubjectHolder.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2009-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.kerberos.authentication;
+
+import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosClient;
+
+import javax.security.auth.Subject;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
Holds the Subject of the currently authenticated user, since this
+ * Jaas object also has the credentials, and permits creating new
+ * credentials against other Kerberos services.
+ * @author Bogdan Mustiata
+ * @see SunJaasKerberosClient
+ * @see org.springframework.security.kerberos.authentication.KerberosAuthenticationProvider
+ */
+public class JaasSubjectHolder {
+ private Subject jaasSubject;
+ private String username;
+
+ private Map savedTokens = new HashMap();
+
+ public JaasSubjectHolder(Subject jaasSubject) {
+ this.jaasSubject = jaasSubject;
+ }
+
+ public JaasSubjectHolder(Subject jaasSubject, String username) {
+ this.jaasSubject = jaasSubject;
+ this.username = username;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public Subject getJaasSubject() {
+ return jaasSubject;
+ }
+
+ public void addToken(String targetService, byte[] outToken) {
+ this.savedTokens.put(targetService, outToken);
+ }
+
+ public byte[] getToken(String principalName) {
+ return savedTokens.get(principalName);
+ }
+}
diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthentication.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthentication.java
new file mode 100644
index 0000000..0d59c8c
--- /dev/null
+++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthentication.java
@@ -0,0 +1,6 @@
+package org.springframework.security.kerberos.authentication;
+
+public interface KerberosAuthentication {
+
+ JaasSubjectHolder getJaasSubjectHolder();
+}
diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProvider.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProvider.java
index 440f6ee..1f6403e 100644
--- a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProvider.java
+++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProvider.java
@@ -26,6 +26,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
* {@link AuthenticationProvider} for kerberos.
*
* @author Mike Wiesner
+ * @author Bogdan Mustiata
* @since 1.0
*/
public class KerberosAuthenticationProvider implements AuthenticationProvider {
@@ -37,10 +38,10 @@ public class KerberosAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
- String validatedUsername = kerberosClient.login(auth.getName(), auth.getCredentials().toString());
- UserDetails userDetails = this.userDetailsService.loadUserByUsername(validatedUsername);
- UsernamePasswordAuthenticationToken output = new UsernamePasswordAuthenticationToken(userDetails,
- auth.getCredentials(), userDetails.getAuthorities());
+ JaasSubjectHolder subjectHolder = kerberosClient.login(auth.getName(), auth.getCredentials().toString());
+ UserDetails userDetails = this.userDetailsService.loadUserByUsername(subjectHolder.getUsername());
+ KerberosUsernamePasswordAuthenticationToken output = new KerberosUsernamePasswordAuthenticationToken(
+ userDetails, auth.getCredentials(), userDetails.getAuthorities(), subjectHolder);
output.setDetails(authentication.getDetails());
return output;
diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosClient.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosClient.java
index 27aa538..560d681 100644
--- a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosClient.java
+++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosClient.java
@@ -19,11 +19,12 @@ package org.springframework.security.kerberos.authentication;
/**
*
* @author Mike Wiesner
+ * @author Bogdan Mustiata
* @since 1.0
* @version $Id$
*/
public interface KerberosClient {
- public String login(String username, String password);
+ public JaasSubjectHolder login(String username, String password);
}
diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosMultiTier.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosMultiTier.java
new file mode 100644
index 0000000..e2a7755
--- /dev/null
+++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosMultiTier.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2009-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.kerberos.authentication;
+
+import org.ietf.jgss.*;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+
+import javax.security.auth.Subject;
+import java.security.PrivilegedAction;
+
+/**
+ *
Allows creating tickets against other service principals storing the
+ * tickets in the KerberosAuthentication's JaasSubjectHolder.
+ * @author Bogdan Mustiata
+ */
+public class KerberosMultiTier {
+ public static final String KERBEROS_OID_STRING = "1.2.840.113554.1.2.2";
+
+ public static final Oid KERBEROS_OID = createOid(KERBEROS_OID_STRING);
+
+ /**
+ * Create a new ticket for the
+ * @param authentication
+ * @param username
+ * @param lifetimeInSeconds
+ * @param targetService
+ * @return
+ */
+ public static Authentication authenticateService(Authentication authentication,
+ final String username,
+ final int lifetimeInSeconds,
+ final String targetService) {
+
+ KerberosAuthentication kerberosAuthentication = (KerberosAuthentication)authentication;
+ final JaasSubjectHolder jaasSubjectHolder = kerberosAuthentication.getJaasSubjectHolder();
+ Subject subject = jaasSubjectHolder.getJaasSubject();
+
+ Subject.doAs(subject, new PrivilegedAction