Quellcode durchsuchen

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

Luke Taylor vor 17 Jahren
Ursprung
Commit
5738a51040

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

@@ -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 - 0
core/src/main/java/org/springframework/security/config/LdapConfigUtils.java

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

+ 6 - 46
core/src/main/java/org/springframework/security/config/LdapProviderBeanDefinitionParser.java

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

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

@@ -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 - 0
core/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java

@@ -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());

+ 20 - 1
core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc

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

+ 34 - 1
core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd

@@ -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 @@
     </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 - 0
core/src/test/java/org/springframework/security/config/LdapUserServiceBeanDefinitionParserTests.java

@@ -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 - 0
samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml

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