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..4d5a2c3 --- /dev/null +++ b/spring-security-kerberos-client/src/test/java/org/springframework/security/kerberos/client/TestMultiTierAuthentication.java @@ -0,0 +1,180 @@ +/* + * 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 { + @Test + public void testServer() throws Exception { + MiniKdc kdc = getKdc(); + File workDir = getWorkDir(); + + File webTierKeytabFile = new File(workDir, "webtier.keytab"); + kdc.createKeyabFile(webTierKeytabFile, "HTTP/webtier@EXAMPLE.COM", "secret"); + + File serviceTierKeytabFile = new File(workDir, "servicetier.keytab"); + kdc.createKeyabFile(serviceTierKeytabFile, "HTTP/servicetier@EXAMPLE.COM", "secret"); + + // + // User logs in as user1/secret + // + KerberosAuthenticationProvider kerberosAuthProvider = + createUserPassAuthenticator(/* debug: */ true); + + Authentication authentication = kerberosAuthProvider + .authenticate(new UsernamePasswordAuthenticationToken("user1", "secret")); + + assertEquals("user1@EXAMPLE.COM", 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, "user1", 3600, "HTTP/webtier@EXAMPLE.COM"); + + byte[] tokenData = KerberosMultiTier + .getTokenForService(authentication, "HTTP/webtier@EXAMPLE.COM"); + + assertNotNull(tokenData); + assertTrue(tokenData.length != 0); + + // + // The service HTTP/webtier@EXAMPLE.COM authenticates via tokens. + // + KerberosServiceAuthenticationProvider webTierAuthenticatorProvider = + createServiceAuthenticator( + true, + "HTTP/webtier@EXAMPLE.COM", + "EXAMPLE.COM", + 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("user1@EXAMPLE.COM", webTierAuthentication.getName()); + + webTierAuthentication = KerberosMultiTier.authenticateService( + webTierAuthentication, "user1@EXAMPLE.COM", 3600, "HTTP/servicetier@EXAMPLE.COM"); + + byte[] workplaceTokenData = KerberosMultiTier.getTokenForService( + webTierAuthentication, "HTTP/servicetier@EXAMPLE.COM"); + + // + // The service HTTP/icr@EXAMPLE.COM authenticates via tokens. + // + webTierAuthenticatorProvider = + createServiceAuthenticator( + true, + "HTTP/servicetier@EXAMPLE.COM", + "EXAMPLE.COM", + 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("user1@EXAMPLE.COM", 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() { + @Override + public Object run() { + runAuthentication(jaasSubjectHolder, username, lifetimeInSeconds, targetService); + + return null; + } + }); + + return authentication; + } + + public static byte[] getTokenForService(Authentication authentication, String principalName) { + KerberosAuthentication kerberosAuthentication = (KerberosAuthentication)authentication; + final JaasSubjectHolder jaasSubjectHolder = kerberosAuthentication.getJaasSubjectHolder(); + + return jaasSubjectHolder.getToken(principalName); + } + + private static void runAuthentication(JaasSubjectHolder jaasContext, + String username, + int lifetimeInSeconds, + String targetService) { + try { + GSSManager manager = GSSManager.getInstance(); + GSSName clientName = manager.createName(username, GSSName.NT_USER_NAME); + + GSSCredential clientCredential = manager.createCredential( + clientName, + lifetimeInSeconds, + KERBEROS_OID, + GSSCredential.INITIATE_ONLY + ); + + GSSName serverName = manager.createName(targetService, GSSName.NT_USER_NAME); + + GSSContext securityContext = manager.createContext(serverName, + KERBEROS_OID, + clientCredential, + GSSContext.DEFAULT_LIFETIME); + + securityContext.requestCredDeleg(true); + securityContext.requestInteg(false); + securityContext.requestAnonymity(false); + securityContext.requestMutualAuth(false); + securityContext.requestReplayDet(false); + securityContext.requestSequenceDet(false); + + boolean established = false; + + byte[] outToken = new byte[0]; + + while (!established) { + byte[] inToken = new byte[0]; + outToken = securityContext.initSecContext(inToken, 0, inToken.length); + + established = securityContext.isEstablished(); + } + + jaasContext.addToken(targetService, outToken); + } catch (Exception e) { + throw new BadCredentialsException("Kerberos authentication failed", e); + } + } + + private static Oid createOid(String oid) + { + try { + return new Oid(oid); + } catch (GSSException e) { + throw new IllegalStateException("Unable to instantiate Oid: ", e); + } + } +} diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceRequestToken.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceRequestToken.java index a6c687c..b8b2f65 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceRequestToken.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceRequestToken.java @@ -41,10 +41,13 @@ import org.springframework.security.crypto.codec.Base64; * * @author Mike Wiesner * @author Jeremy Stone + * @author Bogdan Mustiata * @since 1.0 * @see KerberosServiceAuthenticationProvider */ -public class KerberosServiceRequestToken extends AbstractAuthenticationToken { +public class KerberosServiceRequestToken + extends AbstractAuthenticationToken + implements KerberosAuthentication { private static final long serialVersionUID = 395488921064775014L; @@ -54,6 +57,8 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { private final transient KerberosTicketValidation ticketValidation; + private JaasSubjectHolder jaasSubjectHolder; + /** * Creates an authenticated token, normally used as an output of an * authentication provider. @@ -64,12 +69,17 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { * @param token the Kerberos/SPNEGO token * @see UserDetails */ - public KerberosServiceRequestToken(Object principal, KerberosTicketValidation ticketValidation, - Collection authorities, byte[] token) { + public KerberosServiceRequestToken(Object principal, + KerberosTicketValidation ticketValidation, + Collection authorities, + byte[] token) { super(authorities); this.token = token; this.principal = principal; this.ticketValidation = ticketValidation; + this.jaasSubjectHolder = new JaasSubjectHolder( + ticketValidation.subject(), + ticketValidation.username()); super.setAuthenticated(true); } @@ -225,4 +235,8 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { return encrypt(data, 0, data.length); } + @Override + public JaasSubjectHolder getJaasSubjectHolder() { + return jaasSubjectHolder; + } } diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosTicketValidation.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosTicketValidation.java index 4707a40..e9a7182 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosTicketValidation.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosTicketValidation.java @@ -13,13 +13,24 @@ import org.ietf.jgss.GSSContext; public class KerberosTicketValidation { private final String username; + private final Subject subject; private final byte[] responseToken; private final GSSContext gssContext; - private final String servicePrincipal; public KerberosTicketValidation(String username, String servicePrincipal, byte[] responseToken, GSSContext gssContext) { + final HashSet princs = new HashSet(); + princs.add(new KerberosPrincipal(servicePrincipal)); + this.username = username; - this.servicePrincipal = servicePrincipal; + this.subject = new Subject(false, princs, new HashSet(), new HashSet()); + this.responseToken = responseToken; + this.gssContext = gssContext; + } + + + public KerberosTicketValidation(String username, Subject subject, byte[] responseToken, GSSContext gssContext) { + this.username = username; + this.subject = subject; this.responseToken = responseToken; this.gssContext = gssContext; } @@ -37,9 +48,7 @@ public class KerberosTicketValidation { } public Subject subject() { - final HashSet princs = new HashSet(); - princs.add(new KerberosPrincipal(servicePrincipal)); - return new Subject(false, princs, new HashSet(), new HashSet()); + return this.subject; } } \ No newline at end of file diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosUsernamePasswordAuthenticationToken.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosUsernamePasswordAuthenticationToken.java new file mode 100644 index 0000000..39e1f7f --- /dev/null +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosUsernamePasswordAuthenticationToken.java @@ -0,0 +1,61 @@ +/* + * 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.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +/** + *

Holds the Username/Password as well as the JAAS Subject allowing + * multi-tier authentications using Kerberos.

+ * + *

The JAAS Subject has in its private credentials the Kerberos tickets + * for generating new tickets against other service principals using + * KerberosMultiTier.authenticateService()

+ * + * @author Bogdan Mustiata + * @see KerberosAuthenticationProvider + * @see KerberosMultiTier + */ +public class KerberosUsernamePasswordAuthenticationToken + extends UsernamePasswordAuthenticationToken + implements KerberosAuthentication { + + private final JaasSubjectHolder jaasSubjectHolder; + + /** + *

Creates an authentication token that holds the username and password, and the Subject + * that the user will need to create new authentication tokens against other services.

+ * @param principal + * @param credentials + * @param authorities + * @param subjectHolder + */ + public KerberosUsernamePasswordAuthenticationToken(Object principal, + Object credentials, + Collection authorities, + JaasSubjectHolder subjectHolder) { + super(principal, credentials, authorities); + this.jaasSubjectHolder = subjectHolder; + } + + @Override + public JaasSubjectHolder getJaasSubjectHolder() { + return jaasSubjectHolder; + } +} diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/JaasUtil.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/JaasUtil.java new file mode 100644 index 0000000..e207a9e --- /dev/null +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/JaasUtil.java @@ -0,0 +1,40 @@ +/* + * 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.sun; + +import javax.security.auth.Subject; +import java.security.Principal; +import java.util.HashSet; + +/** + * JAAS utility functions. + * @author Bogdan Mustiata + */ +public class JaasUtil { + /** + * Copy the principal and the credentials into a new Subject. + * @param subject + * @return + */ + public static Subject copySubject(Subject subject) { + Subject subjectCopy = new Subject(false, + new HashSet(subject.getPrincipals()), + new HashSet(subject.getPublicCredentials()), + new HashSet(subject.getPrivateCredentials())); + + return subjectCopy; + } +} diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosClient.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosClient.java index 3167c8f..35b8485 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosClient.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosClient.java @@ -18,6 +18,7 @@ package org.springframework.security.kerberos.authentication.sun; import java.io.IOException; import java.util.HashMap; +import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; @@ -32,6 +33,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.kerberos.authentication.KerberosClient; +import org.springframework.security.kerberos.authentication.JaasSubjectHolder; /** * Implementation of {@link KerberosClient} which uses the SUN JAAS @@ -40,39 +42,55 @@ import org.springframework.security.kerberos.authentication.KerberosClient; * is needed. * * @author Mike Wiesner + * @author Bogdan Mustiata * @since 1.0 */ public class SunJaasKerberosClient implements KerberosClient { private boolean debug = false; + private boolean multiTier = false; private static final Log LOG = LogFactory.getLog(SunJaasKerberosClient.class); @Override - public String login(String username, String password) { + public JaasSubjectHolder login(String username, String password) { LOG.debug("Trying to authenticate " + username + " with Kerberos"); - String validatedUsername; + JaasSubjectHolder result; try { - LoginContext loginContext = new LoginContext("", null, new KerberosClientCallbackHandler(username, password), + LoginContext loginContext = new LoginContext("", null, + new KerberosClientCallbackHandler(username, password), new LoginConfig(this.debug)); loginContext.login(); + + Subject jaasSubject = loginContext.getSubject(); + if (LOG.isDebugEnabled()) { - LOG.debug("Kerberos authenticated user: "+loginContext.getSubject()); + LOG.debug("Kerberos authenticated user: "+ jaasSubject); + } + + String validatedUsername = jaasSubject.getPrincipals().iterator().next().toString(); + Subject subjectCopy = JaasUtil.copySubject(jaasSubject); + result = new JaasSubjectHolder(subjectCopy, validatedUsername); + + if (!multiTier) { + loginContext.logout(); } - validatedUsername = loginContext.getSubject().getPrincipals().iterator().next().toString(); - loginContext.logout(); } catch (LoginException e) { throw new BadCredentialsException("Kerberos authentication failed", e); } - return validatedUsername; + return result; } public void setDebug(boolean debug) { this.debug = debug; } + public void setMultiTier(boolean multiTier) { + this.multiTier = multiTier; + } + private static class LoginConfig extends Configuration { private boolean debug; diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java index c547cf0..395d483 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java @@ -28,6 +28,7 @@ import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; +import com.sun.security.jgss.GSSUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ietf.jgss.GSSContext; @@ -39,6 +40,8 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.kerberos.authentication.JaasSubjectHolder; +import org.springframework.security.kerberos.authentication.KerberosMultiTier; import org.springframework.security.kerberos.authentication.KerberosTicketValidation; import org.springframework.security.kerberos.authentication.KerberosTicketValidator; import org.springframework.util.Assert; @@ -51,21 +54,33 @@ import org.springframework.util.Assert; * * @author Mike Wiesner * @author Jeremy Stone + * @author Bogdan Mustiata * @since 1.0 */ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, InitializingBean { private String servicePrincipal; + private String realmName; private Resource keyTabLocation; private Subject serviceSubject; private boolean holdOnToGSSContext; private boolean debug = false; + private boolean multiTier = false; private static final Log LOG = LogFactory.getLog(SunJaasKerberosTicketValidator.class); @Override public KerberosTicketValidation validateTicket(byte[] token) { try { - return Subject.doAs(this.serviceSubject, new KerberosValidateAction(token)); + if (!multiTier) { + return Subject.doAs(this.serviceSubject, new KerberosValidateAction(token)); + } + + Subject subjectCopy = JaasUtil.copySubject(serviceSubject); + JaasSubjectHolder subjectHolder = new JaasSubjectHolder(subjectCopy); + + return Subject.doAs(subjectHolder.getJaasSubject(), + new KerberosMultitierValidateAction(token)); + } catch (PrivilegedActionException e) { throw new BadCredentialsException("Kerberos validation not successful", e); @@ -86,7 +101,11 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, { keyTabLocationAsString = keyTabLocationAsString.substring(5); } - LoginConfig loginConfig = new LoginConfig(keyTabLocationAsString, this.servicePrincipal, + LoginConfig loginConfig = new LoginConfig( + keyTabLocationAsString, + this.servicePrincipal, + this.realmName, + this.multiTier, this.debug); Set princ = new HashSet(1); princ.add(new KerberosPrincipal(this.servicePrincipal)); @@ -108,6 +127,23 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, this.servicePrincipal = servicePrincipal; } + /** + * The realm name of the application. + * For web apps this is DOMAIN + * @param realmName + */ + public void setRealmName(String realmName) { + this.realmName = realmName; + } + + /** + * + * @param multiTier + */ + public void setMultiTier(boolean multiTier) { + this.multiTier = multiTier; + } + /** *

The location of the keytab. You can use the normale Spring Resource * prefixes like file: or classpath:, but as the @@ -146,6 +182,69 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, this.holdOnToGSSContext = holdOnToGSSContext; } + + + /** + * This class is needed, because the validation must run with previously generated JAAS subject + * which belongs to the service principal and was loaded out of the keytab during startup. + */ + private class KerberosMultitierValidateAction implements PrivilegedExceptionAction { + byte[] kerberosTicket; + + public KerberosMultitierValidateAction(byte[] kerberosTicket) { + this.kerberosTicket = kerberosTicket; + } + + @Override + public KerberosTicketValidation run() throws Exception { + byte[] responseToken = new byte[0]; + GSSManager manager = GSSManager.getInstance(); + + GSSName serverName = + manager.createName(servicePrincipal, + GSSName.NT_USER_NAME); + + GSSCredential serverCreds = + manager.createCredential(serverName, + GSSCredential.INDEFINITE_LIFETIME, + KerberosMultiTier.KERBEROS_OID, + GSSCredential.INITIATE_AND_ACCEPT); + + GSSContext context = manager.createContext(serverCreds); + + boolean first = true; + while (!context.isEstablished()) { + if (first) { + kerberosTicket = tweakJdkRegression(kerberosTicket); + } + responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length); + serverName = context.getSrcName(); + if (serverName == null) { + throw new BadCredentialsException("GSSContext name of the context initiator is null"); + } + first = false; + } + + Subject subject = GSSUtil.createSubject( + context.getSrcName(), + context.getDelegCred()); + + KerberosTicketValidation result = new KerberosTicketValidation( + context.getSrcName().toString(), + subject, + responseToken, + context); + + if (!holdOnToGSSContext) { + context.dispose(); + } + + return result; + } + } + + + /** * This class is needed, because the validation must run with previously generated JAAS subject * which belongs to the service principal and was loaded out of the keytab during startup. @@ -159,28 +258,33 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, @Override public KerberosTicketValidation run() throws Exception { - byte[] responseToken = new byte[0]; - GSSName gssName = null; - GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null); - boolean first = true; - while (!context.isEstablished()) { - if (first) { - kerberosTicket = tweakJdkRegression(kerberosTicket); - } - responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length); - gssName = context.getSrcName(); - if (gssName == null) { - throw new BadCredentialsException("GSSContext name of the context initiator is null"); - } - first = false; - } - if (!holdOnToGSSContext) { - context.dispose(); - } - return new KerberosTicketValidation(gssName.toString(), servicePrincipal, responseToken, context); + byte[] responseToken = new byte[0]; + GSSName gssName = null; + GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null); + boolean first = true; + while (!context.isEstablished()) { + if (first) { + kerberosTicket = tweakJdkRegression(kerberosTicket); + } + responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length); + gssName = context.getSrcName(); + if (gssName == null) { + throw new BadCredentialsException("GSSContext name of the context initiator is null"); + } + first = false; + } + if (!holdOnToGSSContext) { + context.dispose(); + } + return new KerberosTicketValidation(gssName.toString(), + servicePrincipal, + responseToken, + context); } } + + /** * Normally you need a JAAS config file in order to use the JAAS Kerberos Login Module, * with this class it is not needed and you can have different configurations in one JVM. @@ -188,11 +292,15 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, private static class LoginConfig extends Configuration { private String keyTabLocation; private String servicePrincipalName; + private String realmName; + private boolean multiTier; private boolean debug; - public LoginConfig(String keyTabLocation, String servicePrincipalName, boolean debug) { + public LoginConfig(String keyTabLocation, String servicePrincipalName, String realmName, boolean multiTier, boolean debug) { this.keyTabLocation = keyTabLocation; this.servicePrincipalName = servicePrincipalName; + this.realmName = realmName; + this.multiTier = multiTier; this.debug = debug; } @@ -207,7 +315,14 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, if (this.debug) { options.put("debug", "true"); } - options.put("isInitiator", "false"); + + if (this.realmName != null) { + options.put("realm", realmName); + } + + if (!multiTier) { + options.put("isInitiator", "false"); + } return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), }; diff --git a/spring-security-kerberos-core/src/test/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProviderTest.java b/spring-security-kerberos-core/src/test/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProviderTest.java index a784d01..d49f673 100644 --- a/spring-security-kerberos-core/src/test/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProviderTest.java +++ b/spring-security-kerberos-core/src/test/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProviderTest.java @@ -29,8 +29,6 @@ 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.kerberos.authentication.KerberosAuthenticationProvider; -import org.springframework.security.kerberos.authentication.KerberosClient; /** * Test class for {@link KerberosAuthenticationProvider} @@ -49,6 +47,7 @@ public class KerberosAuthenticationProviderTest { private static final UsernamePasswordAuthenticationToken INPUT_TOKEN = new UsernamePasswordAuthenticationToken(TEST_USER, TEST_PASSWORD); private static final List AUTHORITY_LIST = AuthorityUtils.createAuthorityList("ROLE_ADMIN"); private static final UserDetails USER_DETAILS = new User(TEST_USER, "empty", true, true, true,true, AUTHORITY_LIST); + private static final JaasSubjectHolder JAAS_SUBJECT_HOLDER = new JaasSubjectHolder(null, TEST_USER); @Before public void before() { @@ -63,7 +62,7 @@ public class KerberosAuthenticationProviderTest { @Test public void testLoginOk() throws Exception { when(userDetailsService.loadUserByUsername(TEST_USER)).thenReturn(USER_DETAILS); - when(kerberosClient.login(TEST_USER, TEST_PASSWORD)).thenReturn(TEST_USER); + when(kerberosClient.login(TEST_USER, TEST_PASSWORD)).thenReturn(JAAS_SUBJECT_HOLDER); Authentication authenticate = provider.authenticate(INPUT_TOKEN); diff --git a/spring-security-kerberos-test/src/main/java/org/springframework/security/kerberos/test/MiniKdc.java b/spring-security-kerberos-test/src/main/java/org/springframework/security/kerberos/test/MiniKdc.java index a71a0f5..f11f2d3 100644 --- a/spring-security-kerberos-test/src/main/java/org/springframework/security/kerberos/test/MiniKdc.java +++ b/spring-security-kerberos-test/src/main/java/org/springframework/security/kerberos/test/MiniKdc.java @@ -104,6 +104,7 @@ import java.util.UUID; * * @author Original Hadoop MiniKdc Authors * @author Janne Valkealahti + * @author Bogdan Mustiata * */ public class MiniKdc { @@ -539,4 +540,28 @@ public class MiniKdc { keytab.setEntries(entries); keytab.write(keytabFile); } + + /** + * Creates multiple principals in the KDC and adds them to a keytab file. + * + * @param keytabFile keytab file to add the created principal. + * @param principal The principal to store in the keytab file. + * @param password The password for the principal. + * @throws Exception thrown if the principals or the keytab file could not be created. + */ + public void createKeyabFile(File keytabFile, String principal, String password) throws Exception { + Keytab keytab = new Keytab(); + List entries = new ArrayList(); + + KerberosTime timestamp = new KerberosTime(); + for (Map.Entry entry : KerberosKeyFactory.getKerberosKeys(principal, + password).entrySet()) { + EncryptionKey ekey = entry.getValue(); + byte keyVersion = (byte) ekey.getKeyVersion(); + entries.add(new KeytabEntry(principal, 1L, timestamp, keyVersion, ekey)); + } + + keytab.setEntries(entries); + keytab.write(keytabFile); + } }