SES-10: Enable server side Kerberos login
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2009 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.extensions.kerberos;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
/**
|
||||
* @author Mike Wiesner
|
||||
* @since 1.0
|
||||
* @version $Id$
|
||||
*/
|
||||
public class KerberosAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(KerberosAuthenticationProvider.class);
|
||||
|
||||
private KerberosClient kerberosClient;
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
|
||||
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
|
||||
String validatedUsername = kerberosClient.login(auth.getName(), auth.getCredentials().toString());
|
||||
if (validatedUsername.equalsIgnoreCase(auth.getName()) == false) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.info("Username returned from KDC ("+validatedUsername+") doesn't match with supplied username ("+auth.getName()+")");
|
||||
}
|
||||
throw new BadCredentialsException("Username returned from KDC doesn't match with supplied username");
|
||||
}
|
||||
|
||||
UserDetails userDetails = this.userDetailsService.loadUserByUsername(auth.getName());
|
||||
UsernamePasswordAuthenticationToken output = new UsernamePasswordAuthenticationToken(userDetails, auth.getCredentials(), userDetails.getAuthorities());
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
public boolean supports(Class<? extends Object> authentication) {
|
||||
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
}
|
||||
|
||||
public void setKerberosClient(KerberosClient kerberosClient) {
|
||||
this.kerberosClient = kerberosClient;
|
||||
}
|
||||
|
||||
|
||||
public void setUserDetailsService(UserDetailsService detailsService) {
|
||||
this.userDetailsService = detailsService;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2009 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.extensions.kerberos;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Mike Wiesner
|
||||
* @since 1.0
|
||||
* @version $Id$
|
||||
*/
|
||||
public interface KerberosClient {
|
||||
|
||||
public String login(String username, String password);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2009 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.extensions.kerberos;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.NameCallback;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import javax.security.auth.login.AppConfigurationEntry;
|
||||
import javax.security.auth.login.Configuration;
|
||||
import javax.security.auth.login.LoginContext;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
|
||||
/**
|
||||
* Implementation of {@link KerberosClient} which uses the SUN JAAS
|
||||
* login module, which is included in the SUN JRE, it will not work with an IBM JRE.
|
||||
* The whole configuration is done in this class, no additional JAAS configuration
|
||||
* is needed.
|
||||
*
|
||||
* @author Mike Wiesner
|
||||
* @since 1.0
|
||||
* @version $Id$
|
||||
*/
|
||||
public class SunJaasKerberosClient implements KerberosClient, InitializingBean {
|
||||
|
||||
private boolean debug = false;
|
||||
private String krbConfLocation;
|
||||
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(SunJaasKerberosClient.class);
|
||||
|
||||
@Override
|
||||
public String login(String username, String password) {
|
||||
LOG.debug("Trying to authenticate " + username + " with Kerberos");
|
||||
String validatedUsername;
|
||||
|
||||
try {
|
||||
LoginContext loginContext = new LoginContext("", null, new KerberosClientCallbackHandler(username, password),
|
||||
new LoginConfig(this.debug));
|
||||
loginContext.login();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Kerberos authenticated user: "+loginContext.getSubject());
|
||||
}
|
||||
validatedUsername = loginContext.getSubject().getPrincipals().iterator().next().toString();
|
||||
loginContext.logout();
|
||||
} catch (LoginException e) {
|
||||
throw new BadCredentialsException("Kerberos authentication failed", e);
|
||||
}
|
||||
return validatedUsername;
|
||||
|
||||
}
|
||||
|
||||
public void setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
|
||||
public void setKrbConfLocation(String krbConfLocation) {
|
||||
this.krbConfLocation = krbConfLocation;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (krbConfLocation != null) {
|
||||
System.setProperty("java.security.krb5.conf", krbConfLocation);
|
||||
}
|
||||
if (debug) {
|
||||
System.setProperty("sun.security.krb5.debug", "true");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static class LoginConfig extends Configuration {
|
||||
private boolean debug;
|
||||
|
||||
public LoginConfig(boolean debug) {
|
||||
super();
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
|
||||
HashMap<String, String> options = new HashMap<String, String>();
|
||||
options.put("storeKey", "true");
|
||||
if (debug) {
|
||||
options.put("debug", "true");
|
||||
}
|
||||
|
||||
return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
|
||||
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class KerberosClientCallbackHandler implements CallbackHandler {
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public KerberosClientCallbackHandler(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||
for (Callback callback : callbacks) {
|
||||
if (callback instanceof NameCallback) {
|
||||
NameCallback ncb = (NameCallback) callback;
|
||||
ncb.setName(username);
|
||||
} else if (callback instanceof PasswordCallback) {
|
||||
PasswordCallback pwcb = (PasswordCallback) callback;
|
||||
pwcb.setPassword(password.toCharArray());
|
||||
} else {
|
||||
throw new UnsupportedCallbackException(callback, "We got a " + callback.getClass().getCanonicalName()
|
||||
+ ", but only NameCallback and PasswordCallback is supported");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2009 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.extensions.kerberos;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Test class for {@link KerberosAuthenticationProvider}
|
||||
*
|
||||
* @author Mike Wiesner
|
||||
* @since 1.0
|
||||
* @version $Id$
|
||||
*/
|
||||
public class KerberosAuthenticationProviderTest {
|
||||
|
||||
private KerberosAuthenticationProvider provider;
|
||||
private KerberosClient kerberosClient;
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
private static final String TEST_USER = "Testuser@SPRINGSOURCE.ORG";
|
||||
private static final String TEST_PASSWORD = "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 UserDetails USER_DETAILS = new User(TEST_USER, "empty", true, true, true,true, AUTHORITY_LIST);
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
// mocking
|
||||
this.kerberosClient = mock(KerberosClient.class);
|
||||
this.userDetailsService = mock(UserDetailsService.class);
|
||||
this.provider = new KerberosAuthenticationProvider();
|
||||
this.provider.setKerberosClient(kerberosClient);
|
||||
this.provider.setUserDetailsService(userDetailsService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginOk() throws Exception {
|
||||
when(userDetailsService.loadUserByUsername(TEST_USER)).thenReturn(USER_DETAILS);
|
||||
when(kerberosClient.login(TEST_USER, TEST_PASSWORD)).thenReturn(TEST_USER);
|
||||
|
||||
Authentication authenticate = provider.authenticate(INPUT_TOKEN);
|
||||
|
||||
verify(kerberosClient).login(TEST_USER, TEST_PASSWORD);
|
||||
|
||||
assertNotNull(authenticate);
|
||||
assertEquals(TEST_USER, authenticate.getName());
|
||||
assertEquals(USER_DETAILS, authenticate.getPrincipal());
|
||||
assertEquals(TEST_PASSWORD, authenticate.getCredentials());
|
||||
assertEquals(AUTHORITY_LIST, authenticate.getAuthorities());
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user