diff --git a/build.gradle b/build.gradle index df58c0c..96efe92 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ configure(allprojects) { apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' + apply plugin: 'propdeps' group = 'org.springframework.security.kerberos' @@ -123,8 +124,8 @@ project('spring-security-kerberos-client') { dependencies { compile project(":spring-security-kerberos-core") compile "org.springframework:spring-web:$springVersion" - compile "org.springframework.security:spring-security-ldap:$springSecurityVersion" compile "org.apache.httpcomponents:httpclient:$httpclientVersion" + optional("org.springframework.security:spring-security-ldap:$springSecurityVersion") testCompile project(":spring-security-kerberos-test") testCompile "org.springframework.boot:spring-boot-autoconfigure:$springBootVersion" testRuntime "org.apache.tomcat.embed:tomcat-embed-core:$tomcatEmbedVersion" @@ -168,6 +169,7 @@ configure(sampleServerProjects()) { apply plugin: 'spring-boot' dependencies { compile project(":spring-security-kerberos-samples-common") + compile project(":spring-security-kerberos-client") compile "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion" compile "org.springframework.security:spring-security-ldap:$springSecurityVersion" testCompile "org.springframework:spring-test:$springVersion" diff --git a/spring-security-kerberos-client/src/main/java/org/springframework/security/extensions/kerberos/client/KerberosLdapContextSource.java b/spring-security-kerberos-client/src/main/java/org/springframework/security/extensions/kerberos/client/KerberosLdapContextSource.java index 1d8559c..5240e4c 100644 --- a/spring-security-kerberos-client/src/main/java/org/springframework/security/extensions/kerberos/client/KerberosLdapContextSource.java +++ b/spring-security-kerberos-client/src/main/java/org/springframework/security/extensions/kerberos/client/KerberosLdapContextSource.java @@ -63,77 +63,87 @@ import org.springframework.util.Assert; * @author Nelson Rodrigues * */ -public class KerberosLdapContextSource extends DefaultSpringSecurityContextSource implements - InitializingBean { +public class KerberosLdapContextSource extends DefaultSpringSecurityContextSource implements InitializingBean { - private Configuration loginConfig; + private Configuration loginConfig; - public KerberosLdapContextSource(String url) { - super(url); - } + /** + * Instantiates a new kerberos ldap context source. + * + * @param url the url + */ + public KerberosLdapContextSource(String url) { + super(url); + } - public KerberosLdapContextSource(List urls, String baseDn) { - super(urls, baseDn); - } + /** + * Instantiates a new kerberos ldap context source. + * + * @param urls the urls + * @param baseDn the base dn + */ + public KerberosLdapContextSource(List urls, String baseDn) { + super(urls, baseDn); + } - /** - * The login configuration to get the serviceSubject from LoginContext - * - * @param loginConfig - */ - public void setLoginConfig(Configuration loginConfig) { - this.loginConfig = loginConfig; - } + @Override + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + Assert.notNull(this.loginConfig, "loginConfig must be specified"); + } - @Override - public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); - Assert.notNull(this.loginConfig, "loginConfig must be specified"); - } + @SuppressWarnings("unchecked") + @Override + protected DirContext getDirContextInstance(final @SuppressWarnings("rawtypes") Hashtable environment) + throws NamingException { + environment.put(Context.SECURITY_AUTHENTICATION, "GSSAPI"); - private Subject login() throws AuthenticationException { - try { - LoginContext lc = new LoginContext(KerberosLdapContextSource.class.getSimpleName(), - null, null, this.loginConfig); + Subject serviceSubject = login(); - lc.login(); + final NamingException[] suppressedException = new NamingException[] { null }; + DirContext dirContext = Subject.doAs(serviceSubject, new PrivilegedAction() { - return lc.getSubject(); - } catch (LoginException e) { - AuthenticationException ae = new AuthenticationException(e.getMessage()); - ae.initCause(e); - throw ae; - } - } + @Override + public DirContext run() { + try { + return KerberosLdapContextSource.super.getDirContextInstance(environment); + } catch (NamingException e) { + suppressedException[0] = e; + return null; + } + } + }); - @SuppressWarnings("unchecked") - @Override - protected DirContext getDirContextInstance( - final @SuppressWarnings("rawtypes") Hashtable environment) throws NamingException { - environment.put(Context.SECURITY_AUTHENTICATION, "GSSAPI"); + if (suppressedException[0] != null) { + throw suppressedException[0]; + } - Subject serviceSubject = login(); + return dirContext; + } - final NamingException[] suppressedException = new NamingException[] { null }; - DirContext dirContext = Subject.doAs(serviceSubject, new PrivilegedAction() { + /** + * The login configuration to get the serviceSubject from LoginContext + * + * @param loginConfig the login config + */ + public void setLoginConfig(Configuration loginConfig) { + this.loginConfig = loginConfig; + } - @Override - public DirContext run() { - try { - return KerberosLdapContextSource.super.getDirContextInstance(environment); - } catch (NamingException e) { - suppressedException[0] = e; - return null; - } - } - }); + private Subject login() throws AuthenticationException { + try { + LoginContext lc = new LoginContext(KerberosLdapContextSource.class.getSimpleName(), null, null, + this.loginConfig); - if (suppressedException[0] != null) { - throw suppressedException[0]; - } + lc.login(); - return dirContext; - } + return lc.getSubject(); + } catch (LoginException e) { + AuthenticationException ae = new AuthenticationException(e.getMessage()); + ae.initCause(e); + throw ae; + } + } } diff --git a/spring-security-kerberos-client/src/main/java/org/springframework/security/extensions/kerberos/client/config/SunJaasKrb5LoginConfig.java b/spring-security-kerberos-client/src/main/java/org/springframework/security/extensions/kerberos/client/config/SunJaasKrb5LoginConfig.java index 2190847..68f28c7 100644 --- a/spring-security-kerberos-client/src/main/java/org/springframework/security/extensions/kerberos/client/config/SunJaasKrb5LoginConfig.java +++ b/spring-security-kerberos-client/src/main/java/org/springframework/security/extensions/kerberos/client/config/SunJaasKrb5LoginConfig.java @@ -20,7 +20,10 @@ import java.util.HashMap; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.util.Assert; @@ -29,74 +32,81 @@ import org.springframework.util.Assert; * Krb5LoginModule. * * @author Nelson Rodrigues + * @author Janne Valkealahti * */ public class SunJaasKrb5LoginConfig extends Configuration implements InitializingBean { - private String servicePrincipal; - private Resource keyTabLocation; - private Boolean useTicketCache = false; - private Boolean isInitiator = false; - private Boolean debug = false; + private static final Log LOG = LogFactory.getLog(SunJaasKrb5LoginConfig.class); - private String keyTabExternalForm; + private String servicePrincipal; + private Resource keyTabLocation; + private Boolean useTicketCache = false; + private Boolean isInitiator = false; + private Boolean debug = false; + private String keyTabLocationAsString; - public void setServicePrincipal(String servicePrincipal) { - this.servicePrincipal = servicePrincipal; - } + public void setServicePrincipal(String servicePrincipal) { + this.servicePrincipal = servicePrincipal; + } - public void setKeyTabLocation(Resource keyTabLocation) { - this.keyTabLocation = keyTabLocation; - } + public void setKeyTabLocation(Resource keyTabLocation) { + this.keyTabLocation = keyTabLocation; + } - public void setUseTicketCache(Boolean useTicketCache) { - this.useTicketCache = useTicketCache; - } + public void setUseTicketCache(Boolean useTicketCache) { + this.useTicketCache = useTicketCache; + } - public void setIsInitiator(Boolean isInitiator) { - this.isInitiator = isInitiator; - } + public void setIsInitiator(Boolean isInitiator) { + this.isInitiator = isInitiator; + } - public void setDebug(Boolean debug) { - this.debug = debug; - } + public void setDebug(Boolean debug) { + this.debug = debug; + } - @Override - public void afterPropertiesSet() throws Exception { - Assert.hasText(servicePrincipal, "servicePrincipal must be specified"); + @Override + public void afterPropertiesSet() throws Exception { + Assert.hasText(servicePrincipal, "servicePrincipal must be specified"); - if (!useTicketCache) { - Assert.notNull(keyTabLocation, - "keyTabLocation must be specified when useTicketCache is false"); - this.keyTabExternalForm = keyTabLocation.getURL().toExternalForm(); - } - } + if (keyTabLocation != null && keyTabLocation instanceof ClassPathResource) { + LOG.warn("Your keytab is in the classpath. This file needs special protection and shouldn't be in the classpath. JAAS may also not be able to load this file from classpath."); + } - @Override - public AppConfigurationEntry[] getAppConfigurationEntry(String name) { - HashMap options = new HashMap(); + if (!useTicketCache) { + Assert.notNull(keyTabLocation, "keyTabLocation must be specified when useTicketCache is false"); + keyTabLocationAsString = keyTabLocation.getURL().toExternalForm(); + if (keyTabLocationAsString.startsWith("file:")) { + keyTabLocationAsString = keyTabLocationAsString.substring(5); + } + } + } - options.put("principal", this.servicePrincipal); + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + HashMap options = new HashMap(); - if (this.keyTabLocation != null) { - options.put("useKeyTab", "true"); - options.put("keyTab", this.keyTabExternalForm); - options.put("storeKey", "true"); - } + options.put("principal", this.servicePrincipal); - options.put("doNotPrompt", "true"); + if (this.keyTabLocation != null) { + options.put("useKeyTab", "true"); + options.put("keyTab", keyTabLocationAsString); + options.put("storeKey", "true"); + } - if (useTicketCache) { - options.put("useTicketCache", "true"); - options.put("renewTGT", "true"); - } + options.put("doNotPrompt", "true"); - options.put("isInitiator", this.isInitiator.toString()); - options.put("debug", this.debug.toString()); + if (useTicketCache) { + options.put("useTicketCache", "true"); + options.put("renewTGT", "true"); + } - return new AppConfigurationEntry[] { new AppConfigurationEntry( - "com.sun.security.auth.module.Krb5LoginModule", - AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), }; - } + options.put("isInitiator", this.isInitiator.toString()); + options.put("debug", this.debug.toString()); + + return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), }; + } } diff --git a/spring-security-kerberos-samples/sec-server-win-auth/src/main/java/demo/app/WebSecurityConfig.java b/spring-security-kerberos-samples/sec-server-win-auth/src/main/java/demo/app/WebSecurityConfig.java index f32f11d..544ebd7 100644 --- a/spring-security-kerberos-samples/sec-server-win-auth/src/main/java/demo/app/WebSecurityConfig.java +++ b/spring-security-kerberos-samples/sec-server-win-auth/src/main/java/demo/app/WebSecurityConfig.java @@ -9,16 +9,16 @@ import org.springframework.security.config.annotation.authentication.builders.Au import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; -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.extensions.kerberos.KerberosServiceAuthenticationProvider; import org.springframework.security.extensions.kerberos.SunJaasKerberosTicketValidator; +import org.springframework.security.extensions.kerberos.client.KerberosLdapContextSource; +import org.springframework.security.extensions.kerberos.client.config.SunJaasKrb5LoginConfig; import org.springframework.security.extensions.kerberos.web.SpnegoAuthenticationProcessingFilter; import org.springframework.security.extensions.kerberos.web.SpnegoEntryPoint; import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider; +import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; +import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper; +import org.springframework.security.ldap.userdetails.LdapUserDetailsService; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; @Configuration @@ -37,6 +37,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Value("${app.keytab-location}") private String keytabLocation; + @Value("${app.ldap-search-base}") + private String ldapSearchBase; + + @Value("${app.ldap-search-filter}") + private String ldapSearchFilter; + @Override protected void configure(HttpSecurity http) throws Exception { http @@ -87,7 +93,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() { KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider(); provider.setTicketValidator(sunJaasKerberosTicketValidator()); - provider.setUserDetailsService(dummyUserDetailsService()); + provider.setUserDetailsService(ldapUserDetailsService()); return provider; } @@ -101,17 +107,24 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { } @Bean - public DummyUserDetailsService dummyUserDetailsService() { - return new DummyUserDetailsService(); + public KerberosLdapContextSource kerberosLdapContextSource() { + KerberosLdapContextSource contextSource = new KerberosLdapContextSource(adServer); + SunJaasKrb5LoginConfig loginConfig = new SunJaasKrb5LoginConfig(); + loginConfig.setKeyTabLocation(new FileSystemResource(keytabLocation)); + loginConfig.setServicePrincipal(servicePrincipal); + loginConfig.setDebug(true); + loginConfig.setIsInitiator(true); + contextSource.setLoginConfig(loginConfig); + return contextSource; } - static class DummyUserDetailsService implements UserDetailsService { - - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - return new User(username, "notUsed", true, true, true, true, - AuthorityUtils.createAuthorityList("ROLE_USER")); - } - + @Bean + public LdapUserDetailsService ldapUserDetailsService() { + FilterBasedLdapUserSearch userSearch = + new FilterBasedLdapUserSearch(ldapSearchBase, ldapSearchFilter, kerberosLdapContextSource()); + LdapUserDetailsService service = new LdapUserDetailsService(userSearch); + service.setUserDetailsMapper(new LdapUserDetailsMapper()); + return service; } } diff --git a/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/application.yml b/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/application.yml index 855859e..e2dee48 100644 --- a/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/application.yml +++ b/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/application.yml @@ -5,3 +5,5 @@ app: ad-server: ldap://WIN-EKBO0EQ7TS7.example.org/ service-principal: HTTP/neo.example.org@EXAMPLE.ORG keytab-location: /tmp/tomcat.keytab + ldap-search-base: dc=example,dc=org + ldap-search-filter: "(| (userPrincipalName={0}) (sAMAccountName={0}))" diff --git a/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/logback.xml b/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/logback.xml index a7d4dcf..0215b09 100644 --- a/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/logback.xml +++ b/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/logback.xml @@ -4,5 +4,6 @@ + diff --git a/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/templates/hello.html b/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/templates/hello.html index 4d566ef..b6da1ad 100644 --- a/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/templates/hello.html +++ b/spring-security-kerberos-samples/sec-server-win-auth/src/main/resources/templates/hello.html @@ -6,5 +6,6 @@

Hello [[${#httpServletRequest.remoteUser}]]!

+

User principal is [[${#httpServletRequest.userPrincipal}]]!