Fixes for pr #13

- Fixing formatting and javadocs
- Change spring-security-ldap dep to optional to
  reduce risks because we're so close to GA.
- Modified sec-server-win-auth sample to use
  LdapUserDetailsService instead of DummyUserDetailsService.
  Added output of user principal to hello page.
This commit is contained in:
Janne Valkealahti
2015-03-24 17:08:55 +00:00
parent 958c705509
commit 19667a5cec
7 changed files with 162 additions and 123 deletions

View File

@@ -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"

View File

@@ -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<String> 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<String> 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<DirContext>() {
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<DirContext>() {
/**
* 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;
}
}
}

View File

@@ -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<String, String> options = new HashMap<String, String>();
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<String, String> options = new HashMap<String, String>();
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), };
}
}

View File

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

View File

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

View File

@@ -4,5 +4,6 @@
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<logger name="org.springframework.security" level="DEBUG"/>
<logger name="org.springframework.ldap" level="DEBUG"/>
</configuration>

View File

@@ -6,5 +6,6 @@
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<h1 th:inline="text">User principal is [[${#httpServletRequest.userPrincipal}]]!</h1>
</body>
</html>