SEC-651: Support for ldap-user-service bean.
This commit is contained in:
@@ -16,6 +16,7 @@ abstract class Elements {
|
||||
public static final String HTTP = "http";
|
||||
public static final String LDAP_PROVIDER = "ldap-authentication-provider";
|
||||
public static final String LDAP_SERVER = "ldap-server";
|
||||
public static final String LDAP_USER_SERVICE = "ldap-user-service";
|
||||
public static final String PROTECT = "protect";
|
||||
public static final String CONCURRENT_SESSIONS = "concurrent-session-control";
|
||||
public static final String LOGOUT = "logout";
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.springframework.security.config;
|
||||
|
||||
import org.springframework.security.ldap.SpringSecurityContextSource;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
* @since 2.0
|
||||
*/
|
||||
class LdapConfigUtils {
|
||||
|
||||
/** Checks for the presence of a ContextSource instance */
|
||||
private static class ContextSourceSettingPostProcessor implements BeanFactoryPostProcessor, Ordered {
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) throws BeansException {
|
||||
Map beans = bf.getBeansOfType(SpringSecurityContextSource.class);
|
||||
|
||||
if (beans.size() == 0) {
|
||||
throw new SecurityConfigurationException("No SpringSecurityContextSource instances found. Have you " +
|
||||
"added an <" + Elements.LDAP_SERVER + " /> element to your application context?");
|
||||
}
|
||||
|
||||
// else if (beans.size() > 1) {
|
||||
// throw new SecurityConfigurationException("More than one SpringSecurityContextSource instance found. " +
|
||||
// "Please specify a specific server id when configuring your <" + Elements.LDAP_PROVIDER + "> " +
|
||||
// "or <" + Elements.LDAP_USER_SERVICE + ">.");
|
||||
// }
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return LOWEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
|
||||
static void registerPostProcessorIfNecessary(BeanDefinitionRegistry registry) {
|
||||
if (registry.containsBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR)) {
|
||||
return;
|
||||
}
|
||||
|
||||
registry.registerBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR,
|
||||
new RootBeanDefinition(ContextSourceSettingPostProcessor.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +1,22 @@
|
||||
package org.springframework.security.config;
|
||||
|
||||
import org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator;
|
||||
import org.springframework.security.ldap.SpringSecurityContextSource;
|
||||
import org.springframework.security.providers.ldap.LdapAuthenticationProvider;
|
||||
import org.springframework.security.providers.ldap.authenticator.BindAuthenticator;
|
||||
import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor;
|
||||
import org.springframework.security.ui.rememberme.RememberMeServices;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Experimental "security:ldap" namespace configuration.
|
||||
*
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
* @since 2.0
|
||||
@@ -40,7 +28,8 @@ public class LdapProviderBeanDefinitionParser implements BeanDefinitionParser {
|
||||
private static final String ATT_SERVER = "server-ref";
|
||||
|
||||
private static final String OPT_DEFAULT_DN_PATTERN = "uid={0},ou=people";
|
||||
private static final String DEFAULT_GROUP_CONTEXT = "ou=groups";
|
||||
private static final String DEF_GROUP_CONTEXT = "ou=groups";
|
||||
private static final String DEF_GROUP_SEARCH_FILTER = "(uniqueMember={0})";
|
||||
|
||||
|
||||
public BeanDefinition parse(Element elt, ParserContext parserContext) {
|
||||
@@ -57,47 +46,18 @@ public class LdapProviderBeanDefinitionParser implements BeanDefinitionParser {
|
||||
bindAuthenticator.getPropertyValues().addPropertyValue("userDnPatterns", new String[] {OPT_DEFAULT_DN_PATTERN});
|
||||
RootBeanDefinition authoritiesPopulator = new RootBeanDefinition(DefaultLdapAuthoritiesPopulator.class);
|
||||
authoritiesPopulator.getConstructorArgumentValues().addGenericArgumentValue(contextSource);
|
||||
authoritiesPopulator.getConstructorArgumentValues().addGenericArgumentValue(DEFAULT_GROUP_CONTEXT);
|
||||
authoritiesPopulator.getConstructorArgumentValues().addGenericArgumentValue(DEF_GROUP_CONTEXT);
|
||||
// TODO: Change to using uniqueMember as default
|
||||
// authoritiesPopulator.getPropertyValues().addPropertyValue("groupSearchFilter", DEF_GROUP_SEARCH_FILTER);
|
||||
|
||||
RootBeanDefinition ldapProvider = new RootBeanDefinition(LdapAuthenticationProvider.class);
|
||||
ldapProvider.getConstructorArgumentValues().addGenericArgumentValue(bindAuthenticator);
|
||||
ldapProvider.getConstructorArgumentValues().addGenericArgumentValue(authoritiesPopulator);
|
||||
|
||||
registerPostProcessorIfNecessary(parserContext.getRegistry());
|
||||
LdapConfigUtils.registerPostProcessorIfNecessary(parserContext.getRegistry());
|
||||
|
||||
ConfigUtils.getRegisteredProviders(parserContext).add(ldapProvider);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Todo: Move to utility class when we add ldap-user-service, as this check will be needed even if no
|
||||
// provider is added.
|
||||
private static class ContextSourceSettingPostProcessor implements BeanFactoryPostProcessor, Ordered {
|
||||
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) throws BeansException {
|
||||
Map beans = bf.getBeansOfType(SpringSecurityContextSource.class);
|
||||
|
||||
if (beans.size() == 0) {
|
||||
throw new SecurityConfigurationException("No SpringSecurityContextSource instances found. Have you " +
|
||||
"added an <" + Elements.LDAP_SERVER + " /> element to your application context?");
|
||||
} else if (beans.size() > 1) {
|
||||
throw new SecurityConfigurationException("More than one SpringSecurityContextSource instance found. " +
|
||||
"Please specify a specific server id when configuring your <" + Elements.LDAP_PROVIDER + ">");
|
||||
}
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void registerPostProcessorIfNecessary(BeanDefinitionRegistry registry) {
|
||||
if (registry.containsBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR)) {
|
||||
return;
|
||||
}
|
||||
|
||||
registry.registerBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR,
|
||||
new RootBeanDefinition(LdapProviderBeanDefinitionParser.ContextSourceSettingPostProcessor.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.springframework.security.config;
|
||||
|
||||
import org.springframework.security.userdetails.ldap.LdapUserDetailsService;
|
||||
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
|
||||
import org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
* @since 2.0
|
||||
*/
|
||||
public class LdapUserServiceBeanDefinitionParser extends AbstractUserDetailsServiceBeanDefinitionParser {
|
||||
private static final String ATT_SERVER = "server-ref";
|
||||
public static final String ATT_USER_SEARCH_FILTER = "user-search-filter";
|
||||
public static final String ATT_USER_SEARCH_BASE = "user-search-base";
|
||||
public static final String DEF_USER_SEARCH_BASE = "";
|
||||
|
||||
public static final String ATT_GROUP_SEARCH_FILTER = "group-search-filter";
|
||||
public static final String ATT_GROUP_SEARCH_BASE = "group-search-base";
|
||||
public static final String DEF_GROUP_SEARCH_FILTER = "(uniqueMember={0})";
|
||||
public static final String DEF_GROUP_SEARCH_BASE = "ou=groups";
|
||||
|
||||
protected Class getBeanClass(Element element) {
|
||||
return LdapUserDetailsService.class;
|
||||
}
|
||||
|
||||
protected void doParse(Element elt, ParserContext parserContext, BeanDefinitionBuilder builder) {
|
||||
String server = elt.getAttribute(ATT_SERVER);
|
||||
|
||||
if (!StringUtils.hasText(server)) {
|
||||
server = BeanIds.CONTEXT_SOURCE;
|
||||
}
|
||||
|
||||
String userSearchFilter = elt.getAttribute(ATT_USER_SEARCH_FILTER);
|
||||
Assert.hasText(userSearchFilter, "User search filter must be supplied");
|
||||
String userSearchBase = elt.getAttribute(ATT_USER_SEARCH_BASE);
|
||||
|
||||
if (!StringUtils.hasText(userSearchBase)) {
|
||||
userSearchBase = DEF_USER_SEARCH_BASE;
|
||||
}
|
||||
|
||||
String groupSearchFilter = elt.getAttribute(ATT_GROUP_SEARCH_FILTER);
|
||||
String groupSearchBase = elt.getAttribute(ATT_GROUP_SEARCH_BASE);
|
||||
|
||||
if (!StringUtils.hasText(groupSearchFilter)) {
|
||||
groupSearchFilter = DEF_GROUP_SEARCH_FILTER;
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(groupSearchBase)) {
|
||||
groupSearchBase = DEF_GROUP_SEARCH_BASE;
|
||||
}
|
||||
|
||||
RuntimeBeanReference contextSource = new RuntimeBeanReference(server);
|
||||
BeanDefinition search = new RootBeanDefinition(FilterBasedLdapUserSearch.class);
|
||||
search.getConstructorArgumentValues().addIndexedArgumentValue(0, userSearchBase);
|
||||
search.getConstructorArgumentValues().addIndexedArgumentValue(1, userSearchFilter);
|
||||
search.getConstructorArgumentValues().addIndexedArgumentValue(2, contextSource);
|
||||
|
||||
BeanDefinition populator = new RootBeanDefinition(DefaultLdapAuthoritiesPopulator.class);
|
||||
populator.getConstructorArgumentValues().addIndexedArgumentValue(0, contextSource);
|
||||
populator.getConstructorArgumentValues().addIndexedArgumentValue(1, groupSearchBase);
|
||||
populator.getPropertyValues().addPropertyValue("groupSearchFilter", groupSearchFilter);
|
||||
|
||||
builder.addConstructorArg(search);
|
||||
builder.addConstructorArg(populator);
|
||||
|
||||
LdapConfigUtils.registerPostProcessorIfNecessary(parserContext.getRegistry());
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @author Ben Alex
|
||||
* @since 2.0
|
||||
* @version $Id$
|
||||
*/
|
||||
public class SecurityNamespaceHandler extends NamespaceHandlerSupport {
|
||||
@@ -15,6 +16,7 @@ public class SecurityNamespaceHandler extends NamespaceHandlerSupport {
|
||||
// Parsers
|
||||
registerBeanDefinitionParser(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());
|
||||
registerBeanDefinitionParser(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser());
|
||||
registerBeanDefinitionParser(Elements.LDAP_USER_SERVICE, new LdapUserServiceBeanDefinitionParser());
|
||||
registerBeanDefinitionParser(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
|
||||
registerBeanDefinitionParser(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
|
||||
registerBeanDefinitionParser(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
|
||||
|
||||
@@ -66,6 +66,25 @@ ldap-server.attlist &=
|
||||
## Optional root suffix for the embedded LDAP server. Default is "dc=springframework,dc=org"
|
||||
attribute root { xsd:string }?
|
||||
|
||||
ldap-user-service =
|
||||
element ldap-user-service {ldap-us.attlist}
|
||||
ldap-us.attlist &= id?
|
||||
ldap-us.attlist &=
|
||||
## The optional server to use. If omitted, and a default LDAP server is registered (using <ldap-server> with no Id), that server will be used.
|
||||
attribute server-ref {xsd:string}?
|
||||
ldap-us.attlist &=
|
||||
attribute user-search-filter {xsd:string}
|
||||
ldap-us.attlist &=
|
||||
## Search base for user searches. Defaults to "".
|
||||
attribute user-search-base {xsd:string}?
|
||||
ldap-us.attlist &=
|
||||
## Group search filter. Defaults to (uniqueMember={0}).
|
||||
attribute group-search-filter {xsd:string}?
|
||||
ldap-us.attlist &=
|
||||
## Search base for group membership searches. Defaults to "ou=groups".
|
||||
attribute group-search-base {xsd:string}?
|
||||
|
||||
|
||||
|
||||
ldap-authentication-provider =
|
||||
## Sets up an ldap authentication provider
|
||||
@@ -249,7 +268,7 @@ x509.attlist &=
|
||||
|
||||
authentication-provider =
|
||||
## Indicates that the contained user-service should be used as an authentication source.
|
||||
element authentication-provider {ap.attlist & (user-service | jdbc-user-service) & password-encoder}
|
||||
element authentication-provider {ap.attlist & (user-service | jdbc-user-service | ldap-user-service) & password-encoder}
|
||||
ap.attlist &=
|
||||
## Specifies a reference to a separately configured UserDetailsService from which to obtain authentication data.
|
||||
user-service-ref?
|
||||
|
||||
@@ -186,6 +186,39 @@
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="ldap-user-service">
|
||||
<xs:complexType>
|
||||
<xs:attributeGroup ref="security:ldap-us.attlist"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="ldap-us.attlist">
|
||||
<xs:attribute name="id" type="xs:ID">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A bean identifier, used for referring to the bean elsewhere in the context.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="server-ref" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The optional server to use. If omitted, and a default LDAP server is registered (using <ldap-server> with no Id), that server will be used. </xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="user-search-filter" use="required" type="xs:string"/>
|
||||
<xs:attribute name="user-search-base" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Search base for user searches. Defaults to "".</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="group-search-filter" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Group search filter. Defaults to (uniqueMember={0}).</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="group-search-base" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Search base for group membership searches. Defaults to "ou=groups".</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="ldap-authentication-provider">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Sets up an ldap authentication provider</xs:documentation>
|
||||
@@ -578,7 +611,7 @@
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="x509.attlist">
|
||||
<xs:attribute name="subject-regex-match" type="xs:string">
|
||||
<xs:attribute name="subject-principal-regex" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The regular expression used to obtain the username from the certificate's subject. Defaults to matching on the common name using the pattern "CN=(.*?),".</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.springframework.security.config;
|
||||
|
||||
import org.springframework.security.util.InMemoryXmlApplicationContext;
|
||||
import org.springframework.security.userdetails.UserDetailsService;
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.After;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapUserServiceBeanDefinitionParserTests {
|
||||
private InMemoryXmlApplicationContext appCtx;
|
||||
|
||||
@After
|
||||
public void closeAppContext() {
|
||||
if (appCtx != null) {
|
||||
appCtx.close();
|
||||
appCtx = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void minimalConfigurationIsParsedOk() throws Exception {
|
||||
setContext("<ldap-user-service user-search-filter='(uid={0})' /><ldap-server url='ldap://127.0.0.1:343/dc=springframework,dc=org' />");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userServiceReturnsExpectedData() throws Exception {
|
||||
setContext("<ldap-user-service id='ldapUDS' user-search-filter='(uid={0})' group-search-filter='member={0}' /><ldap-server />");
|
||||
|
||||
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
|
||||
UserDetails ben = uds.loadUserByUsername("ben");
|
||||
|
||||
assertEquals(2, ben.getAuthorities().length);
|
||||
}
|
||||
|
||||
private void setContext(String context) {
|
||||
appCtx = new InMemoryXmlApplicationContext(context);
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,12 @@
|
||||
scott/wombat
|
||||
-->
|
||||
<!--
|
||||
|
||||
Uncomment to authenticate against an embedded LDAP server.
|
||||
|
||||
<ldap-server ldif="classpath:users.ldif" />
|
||||
<ldap-authentication-provider />
|
||||
<ldap-user-service user-search-filter="(uid={0}" group-search-filter="member={0}"/>
|
||||
-->
|
||||
|
||||
<authentication-provider>
|
||||
@@ -69,4 +73,5 @@
|
||||
<user name="scott" password="2b58af6dddbd072ed27ffc86725d7d3a" authorities="ROLE_USER" />
|
||||
</user-service>
|
||||
</authentication-provider>
|
||||
|
||||
</beans:beans>
|
||||
Reference in New Issue
Block a user