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]
default_realm = {0}
udp_preference_limit = 1
forwardable = true
[realms]
{0} = '{'

View File

@@ -44,4 +44,43 @@ sn: Service
uid: ldap
userPassword: secret
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.
*
* @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;

View File

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

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 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<? extends GrantedAuthority> authorities, byte[] token) {
public KerberosServiceRequestToken(Object principal,
KerberosTicketValidation ticketValidation,
Collection<? extends GrantedAuthority> 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;
}
}

View File

@@ -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<KerberosPrincipal> princs = new HashSet<KerberosPrincipal>();
princs.add(new KerberosPrincipal(servicePrincipal));
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.gssContext = gssContext;
}
@@ -37,9 +48,7 @@ public class KerberosTicketValidation {
}
public Subject subject() {
final HashSet<KerberosPrincipal> princs = new HashSet<KerberosPrincipal>();
princs.add(new KerberosPrincipal(servicePrincipal));
return new Subject(false, princs, new HashSet<Object>(), new HashSet<Object>());
return this.subject;
}
}

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.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;

View File

@@ -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<Principal> princ = new HashSet<Principal>(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 <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
* 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 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
* 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), };

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.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<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 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);

View File

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