Browse Source

SEC-651: Support for ldap-user-service bean.

2.0.x
Luke Taylor 18 years ago
parent
commit
5738a51040
  1. 1
      core/src/main/java/org/springframework/security/config/Elements.java
  2. 51
      core/src/main/java/org/springframework/security/config/LdapConfigUtils.java
  3. 52
      core/src/main/java/org/springframework/security/config/LdapProviderBeanDefinitionParser.java
  4. 78
      core/src/main/java/org/springframework/security/config/LdapUserServiceBeanDefinitionParser.java
  5. 2
      core/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java
  6. 21
      core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc
  7. 35
      core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd
  8. 44
      core/src/test/java/org/springframework/security/config/LdapUserServiceBeanDefinitionParserTests.java
  9. 5
      samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml

1
core/src/main/java/org/springframework/security/config/Elements.java

@ -16,6 +16,7 @@ abstract class Elements { @@ -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";

51
core/src/main/java/org/springframework/security/config/LdapConfigUtils.java

@ -0,0 +1,51 @@ @@ -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));
}
}

52
core/src/main/java/org/springframework/security/config/LdapProviderBeanDefinitionParser.java

@ -1,34 +1,22 @@ @@ -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 { @@ -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 { @@ -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));
}
}

78
core/src/main/java/org/springframework/security/config/LdapUserServiceBeanDefinitionParser.java

@ -0,0 +1,78 @@ @@ -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());
}
}

2
core/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java

@ -7,6 +7,7 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport; @@ -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 { @@ -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());

21
core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc

@ -66,6 +66,25 @@ ldap-server.attlist &= @@ -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 &= @@ -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?

35
core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd

@ -186,6 +186,39 @@ @@ -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 &lt;ldap-server&gt; 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 @@ @@ -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>

44
core/src/test/java/org/springframework/security/config/LdapUserServiceBeanDefinitionParserTests.java

@ -0,0 +1,44 @@ @@ -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);
}
}

5
samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml

@ -57,8 +57,12 @@ @@ -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 @@ @@ -69,4 +73,5 @@
<user name="scott" password="2b58af6dddbd072ed27ffc86725d7d3a" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</beans:beans>
Loading…
Cancel
Save