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:
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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), };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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}))"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user