Permalink
Browse files

SES-10: Enable server side Kerberos login

  • Loading branch information...
1 parent d91751f commit 84cb1ab36e2e58b0b006bef3c9006bbd52578128 Mike Wiesner committed Dec 23, 2009
@@ -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());
+
+ }
+}

0 comments on commit 84cb1ab

Please sign in to comment.