Added multitier support.

This commit is contained in:
Bogdan Mustiata
2016-04-29 11:49:55 +02:00
parent aaf8f5cb2d
commit e7113fd2b8
16 changed files with 743 additions and 47 deletions

View File

@@ -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"));
}
};
}
}

View File

@@ -18,6 +18,7 @@
[libdefaults] [libdefaults]
default_realm = {0} default_realm = {0}
udp_preference_limit = 1 udp_preference_limit = 1
forwardable = true
[realms] [realms]
{0} = '{' {0} = '{'

View File

@@ -44,4 +44,43 @@ sn: Service
uid: ldap uid: ldap
userPassword: secret userPassword: secret
krb5PrincipalName: ldap/${4}@${2}.${3} krb5PrincipalName: ldap/${4}@${2}.${3}
krb5KeyVersionNumber: 0 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

View File

@@ -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;
/**
* <p>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.</p>
* @author Bogdan Mustiata
* @see SunJaasKerberosClient
* @see org.springframework.security.kerberos.authentication.KerberosAuthenticationProvider
*/
public class JaasSubjectHolder {
private Subject jaasSubject;
private String username;
private Map<String, byte[]> savedTokens = new HashMap<String, byte[]>();
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);
}
}

View File

@@ -0,0 +1,6 @@
package org.springframework.security.kerberos.authentication;
public interface KerberosAuthentication {
JaasSubjectHolder getJaasSubjectHolder();
}

View File

@@ -26,6 +26,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
* {@link AuthenticationProvider} for kerberos. * {@link AuthenticationProvider} for kerberos.
* *
* @author Mike Wiesner * @author Mike Wiesner
* @author Bogdan Mustiata
* @since 1.0 * @since 1.0
*/ */
public class KerberosAuthenticationProvider implements AuthenticationProvider { public class KerberosAuthenticationProvider implements AuthenticationProvider {
@@ -37,10 +38,10 @@ public class KerberosAuthenticationProvider implements AuthenticationProvider {
@Override @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication; UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
String validatedUsername = kerberosClient.login(auth.getName(), auth.getCredentials().toString()); JaasSubjectHolder subjectHolder = kerberosClient.login(auth.getName(), auth.getCredentials().toString());
UserDetails userDetails = this.userDetailsService.loadUserByUsername(validatedUsername); UserDetails userDetails = this.userDetailsService.loadUserByUsername(subjectHolder.getUsername());
UsernamePasswordAuthenticationToken output = new UsernamePasswordAuthenticationToken(userDetails, KerberosUsernamePasswordAuthenticationToken output = new KerberosUsernamePasswordAuthenticationToken(
auth.getCredentials(), userDetails.getAuthorities()); userDetails, auth.getCredentials(), userDetails.getAuthorities(), subjectHolder);
output.setDetails(authentication.getDetails()); output.setDetails(authentication.getDetails());
return output; return output;

View File

@@ -19,11 +19,12 @@ package org.springframework.security.kerberos.authentication;
/** /**
* *
* @author Mike Wiesner * @author Mike Wiesner
* @author Bogdan Mustiata
* @since 1.0 * @since 1.0
* @version $Id$ * @version $Id$
*/ */
public interface KerberosClient { public interface KerberosClient {
public String login(String username, String password); public JaasSubjectHolder login(String username, String password);
} }

View File

@@ -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;
/**
* <p>Allows creating tickets against other service principals storing the
* tickets in the KerberosAuthentication's JaasSubjectHolder.</p>
* @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<Object>() {
@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);
}
}
}

View File

@@ -41,10 +41,13 @@ import org.springframework.security.crypto.codec.Base64;
* *
* @author Mike Wiesner * @author Mike Wiesner
* @author Jeremy Stone * @author Jeremy Stone
* @author Bogdan Mustiata
* @since 1.0 * @since 1.0
* @see KerberosServiceAuthenticationProvider * @see KerberosServiceAuthenticationProvider
*/ */
public class KerberosServiceRequestToken extends AbstractAuthenticationToken { public class KerberosServiceRequestToken
extends AbstractAuthenticationToken
implements KerberosAuthentication {
private static final long serialVersionUID = 395488921064775014L; private static final long serialVersionUID = 395488921064775014L;
@@ -54,6 +57,8 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken {
private final transient KerberosTicketValidation ticketValidation; private final transient KerberosTicketValidation ticketValidation;
private JaasSubjectHolder jaasSubjectHolder;
/** /**
* Creates an authenticated token, normally used as an output of an * Creates an authenticated token, normally used as an output of an
* authentication provider. * authentication provider.
@@ -64,12 +69,17 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken {
* @param token the Kerberos/SPNEGO token * @param token the Kerberos/SPNEGO token
* @see UserDetails * @see UserDetails
*/ */
public KerberosServiceRequestToken(Object principal, KerberosTicketValidation ticketValidation, public KerberosServiceRequestToken(Object principal,
Collection<? extends GrantedAuthority> authorities, byte[] token) { KerberosTicketValidation ticketValidation,
Collection<? extends GrantedAuthority> authorities,
byte[] token) {
super(authorities); super(authorities);
this.token = token; this.token = token;
this.principal = principal; this.principal = principal;
this.ticketValidation = ticketValidation; this.ticketValidation = ticketValidation;
this.jaasSubjectHolder = new JaasSubjectHolder(
ticketValidation.subject(),
ticketValidation.username());
super.setAuthenticated(true); super.setAuthenticated(true);
} }
@@ -225,4 +235,8 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken {
return encrypt(data, 0, data.length); return encrypt(data, 0, data.length);
} }
@Override
public JaasSubjectHolder getJaasSubjectHolder() {
return jaasSubjectHolder;
}
} }

View File

@@ -13,13 +13,24 @@ import org.ietf.jgss.GSSContext;
public class KerberosTicketValidation { public class KerberosTicketValidation {
private final String username; private final String username;
private final Subject subject;
private final byte[] responseToken; private final byte[] responseToken;
private final GSSContext gssContext; private final GSSContext gssContext;
private final String servicePrincipal;
public KerberosTicketValidation(String username, String servicePrincipal, byte[] responseToken, GSSContext gssContext) { public KerberosTicketValidation(String username, String servicePrincipal, byte[] responseToken, GSSContext gssContext) {
final HashSet<KerberosPrincipal> princs = new HashSet<KerberosPrincipal>();
princs.add(new KerberosPrincipal(servicePrincipal));
this.username = username; this.username = username;
this.servicePrincipal = servicePrincipal; this.subject = new Subject(false, princs, new HashSet<Object>(), new HashSet<Object>());
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.responseToken = responseToken;
this.gssContext = gssContext; this.gssContext = gssContext;
} }
@@ -37,9 +48,7 @@ public class KerberosTicketValidation {
} }
public Subject subject() { public Subject subject() {
final HashSet<KerberosPrincipal> princs = new HashSet<KerberosPrincipal>(); return this.subject;
princs.add(new KerberosPrincipal(servicePrincipal));
return new Subject(false, princs, new HashSet<Object>(), new HashSet<Object>());
} }
} }

View File

@@ -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;
/**
* <p>Holds the Username/Password as well as the JAAS Subject allowing
* multi-tier authentications using Kerberos.</p>
*
* <p>The JAAS Subject has in its private credentials the Kerberos tickets
* for generating new tickets against other service principals using
* <code>KerberosMultiTier.authenticateService()</code></p>
*
* @author Bogdan Mustiata
* @see KerberosAuthenticationProvider
* @see KerberosMultiTier
*/
public class KerberosUsernamePasswordAuthenticationToken
extends UsernamePasswordAuthenticationToken
implements KerberosAuthentication {
private final JaasSubjectHolder jaasSubjectHolder;
/**
* <p>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.</p>
* @param principal
* @param credentials
* @param authorities
* @param subjectHolder
*/
public KerberosUsernamePasswordAuthenticationToken(Object principal,
Object credentials,
Collection<? extends GrantedAuthority> authorities,
JaasSubjectHolder subjectHolder) {
super(principal, credentials, authorities);
this.jaasSubjectHolder = subjectHolder;
}
@Override
public JaasSubjectHolder getJaasSubjectHolder() {
return jaasSubjectHolder;
}
}

View File

@@ -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<Principal>(subject.getPrincipals()),
new HashSet<Object>(subject.getPublicCredentials()),
new HashSet<Object>(subject.getPrivateCredentials()));
return subjectCopy;
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.security.kerberos.authentication.sun;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback; import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.NameCallback;
@@ -32,6 +33,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.kerberos.authentication.KerberosClient; import org.springframework.security.kerberos.authentication.KerberosClient;
import org.springframework.security.kerberos.authentication.JaasSubjectHolder;
/** /**
* Implementation of {@link KerberosClient} which uses the SUN JAAS * Implementation of {@link KerberosClient} which uses the SUN JAAS
@@ -40,39 +42,55 @@ import org.springframework.security.kerberos.authentication.KerberosClient;
* is needed. * is needed.
* *
* @author Mike Wiesner * @author Mike Wiesner
* @author Bogdan Mustiata
* @since 1.0 * @since 1.0
*/ */
public class SunJaasKerberosClient implements KerberosClient { public class SunJaasKerberosClient implements KerberosClient {
private boolean debug = false; private boolean debug = false;
private boolean multiTier = false;
private static final Log LOG = LogFactory.getLog(SunJaasKerberosClient.class); private static final Log LOG = LogFactory.getLog(SunJaasKerberosClient.class);
@Override @Override
public String login(String username, String password) { public JaasSubjectHolder login(String username, String password) {
LOG.debug("Trying to authenticate " + username + " with Kerberos"); LOG.debug("Trying to authenticate " + username + " with Kerberos");
String validatedUsername; JaasSubjectHolder result;
try { try {
LoginContext loginContext = new LoginContext("", null, new KerberosClientCallbackHandler(username, password), LoginContext loginContext = new LoginContext("", null,
new KerberosClientCallbackHandler(username, password),
new LoginConfig(this.debug)); new LoginConfig(this.debug));
loginContext.login(); loginContext.login();
Subject jaasSubject = loginContext.getSubject();
if (LOG.isDebugEnabled()) { 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) { } catch (LoginException e) {
throw new BadCredentialsException("Kerberos authentication failed", e); throw new BadCredentialsException("Kerberos authentication failed", e);
} }
return validatedUsername;
return result;
} }
public void setDebug(boolean debug) { public void setDebug(boolean debug) {
this.debug = debug; this.debug = debug;
} }
public void setMultiTier(boolean multiTier) {
this.multiTier = multiTier;
}
private static class LoginConfig extends Configuration { private static class LoginConfig extends Configuration {
private boolean debug; private boolean debug;

View File

@@ -28,6 +28,7 @@ import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration; import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginContext;
import com.sun.security.jgss.GSSUtil;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.ietf.jgss.GSSContext; 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.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.security.authentication.BadCredentialsException; 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.KerberosTicketValidation;
import org.springframework.security.kerberos.authentication.KerberosTicketValidator; import org.springframework.security.kerberos.authentication.KerberosTicketValidator;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@@ -51,21 +54,33 @@ import org.springframework.util.Assert;
* *
* @author Mike Wiesner * @author Mike Wiesner
* @author Jeremy Stone * @author Jeremy Stone
* @author Bogdan Mustiata
* @since 1.0 * @since 1.0
*/ */
public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, InitializingBean { public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, InitializingBean {
private String servicePrincipal; private String servicePrincipal;
private String realmName;
private Resource keyTabLocation; private Resource keyTabLocation;
private Subject serviceSubject; private Subject serviceSubject;
private boolean holdOnToGSSContext; private boolean holdOnToGSSContext;
private boolean debug = false; private boolean debug = false;
private boolean multiTier = false;
private static final Log LOG = LogFactory.getLog(SunJaasKerberosTicketValidator.class); private static final Log LOG = LogFactory.getLog(SunJaasKerberosTicketValidator.class);
@Override @Override
public KerberosTicketValidation validateTicket(byte[] token) { public KerberosTicketValidation validateTicket(byte[] token) {
try { 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) { catch (PrivilegedActionException e) {
throw new BadCredentialsException("Kerberos validation not successful", e); throw new BadCredentialsException("Kerberos validation not successful", e);
@@ -86,7 +101,11 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator,
{ {
keyTabLocationAsString = keyTabLocationAsString.substring(5); keyTabLocationAsString = keyTabLocationAsString.substring(5);
} }
LoginConfig loginConfig = new LoginConfig(keyTabLocationAsString, this.servicePrincipal, LoginConfig loginConfig = new LoginConfig(
keyTabLocationAsString,
this.servicePrincipal,
this.realmName,
this.multiTier,
this.debug); this.debug);
Set<Principal> princ = new HashSet<Principal>(1); Set<Principal> princ = new HashSet<Principal>(1);
princ.add(new KerberosPrincipal(this.servicePrincipal)); princ.add(new KerberosPrincipal(this.servicePrincipal));
@@ -108,6 +127,23 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator,
this.servicePrincipal = servicePrincipal; this.servicePrincipal = servicePrincipal;
} }
/**
* The realm name of the application.
* For web apps this is <code>DOMAIN</code>
* @param realmName
*/
public void setRealmName(String realmName) {
this.realmName = realmName;
}
/**
*
* @param multiTier
*/
public void setMultiTier(boolean multiTier) {
this.multiTier = multiTier;
}
/** /**
* <p>The location of the keytab. You can use the normale Spring Resource * <p>The location of the keytab. You can use the normale Spring Resource
* prefixes like <code>file:</code> or <code>classpath:</code>, but as the * prefixes like <code>file:</code> or <code>classpath:</code>, but as the
@@ -146,6 +182,69 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator,
this.holdOnToGSSContext = holdOnToGSSContext; 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<KerberosTicketValidation> {
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 * 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. * 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 @Override
public KerberosTicketValidation run() throws Exception { public KerberosTicketValidation run() throws Exception {
byte[] responseToken = new byte[0]; byte[] responseToken = new byte[0];
GSSName gssName = null; GSSName gssName = null;
GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null); GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);
boolean first = true; boolean first = true;
while (!context.isEstablished()) { while (!context.isEstablished()) {
if (first) { if (first) {
kerberosTicket = tweakJdkRegression(kerberosTicket); kerberosTicket = tweakJdkRegression(kerberosTicket);
} }
responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length); responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
gssName = context.getSrcName(); gssName = context.getSrcName();
if (gssName == null) { if (gssName == null) {
throw new BadCredentialsException("GSSContext name of the context initiator is null"); throw new BadCredentialsException("GSSContext name of the context initiator is null");
} }
first = false; first = false;
} }
if (!holdOnToGSSContext) { if (!holdOnToGSSContext) {
context.dispose(); context.dispose();
} }
return new KerberosTicketValidation(gssName.toString(), servicePrincipal, responseToken, context); return new KerberosTicketValidation(gssName.toString(),
servicePrincipal,
responseToken,
context);
} }
} }
/** /**
* Normally you need a JAAS config file in order to use the JAAS Kerberos Login Module, * 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. * 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 static class LoginConfig extends Configuration {
private String keyTabLocation; private String keyTabLocation;
private String servicePrincipalName; private String servicePrincipalName;
private String realmName;
private boolean multiTier;
private boolean debug; 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.keyTabLocation = keyTabLocation;
this.servicePrincipalName = servicePrincipalName; this.servicePrincipalName = servicePrincipalName;
this.realmName = realmName;
this.multiTier = multiTier;
this.debug = debug; this.debug = debug;
} }
@@ -207,7 +315,14 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator,
if (this.debug) { if (this.debug) {
options.put("debug", "true"); 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", return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), }; AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), };

View File

@@ -29,8 +29,6 @@ import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; 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} * 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 UsernamePasswordAuthenticationToken INPUT_TOKEN = new UsernamePasswordAuthenticationToken(TEST_USER, TEST_PASSWORD);
private static final List<GrantedAuthority> AUTHORITY_LIST = AuthorityUtils.createAuthorityList("ROLE_ADMIN"); private static final List<GrantedAuthority> 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 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 @Before
public void before() { public void before() {
@@ -63,7 +62,7 @@ public class KerberosAuthenticationProviderTest {
@Test @Test
public void testLoginOk() throws Exception { public void testLoginOk() throws Exception {
when(userDetailsService.loadUserByUsername(TEST_USER)).thenReturn(USER_DETAILS); 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); Authentication authenticate = provider.authenticate(INPUT_TOKEN);

View File

@@ -104,6 +104,7 @@ import java.util.UUID;
* *
* @author Original Hadoop MiniKdc Authors * @author Original Hadoop MiniKdc Authors
* @author Janne Valkealahti * @author Janne Valkealahti
* @author Bogdan Mustiata
* *
*/ */
public class MiniKdc { public class MiniKdc {
@@ -539,4 +540,28 @@ public class MiniKdc {
keytab.setEntries(entries); keytab.setEntries(entries);
keytab.write(keytabFile); 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<KeytabEntry> entries = new ArrayList<KeytabEntry>();
KerberosTime timestamp = new KerberosTime();
for (Map.Entry<EncryptionType, EncryptionKey> 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);
}
} }