浏览代码

SEC-1137: Added support for an external UserDetailsContextMapper using the attribute user-context-mapper-ref.

Luke Taylor 16 年之前
父节点
当前提交
0473cfbfc0

+ 1 - 1
config/src/main/java/org/springframework/security/config/LdapProviderBeanDefinitionParser.java

@@ -97,7 +97,7 @@ public class LdapProviderBeanDefinitionParser implements BeanDefinitionParser {
         ldapProvider.addConstructorArgValue(authenticatorBuilder.getBeanDefinition());
         ldapProvider.addConstructorArgValue(LdapUserServiceBeanDefinitionParser.parseAuthoritiesPopulator(elt, parserContext));
         ldapProvider.addPropertyValue("userDetailsContextMapper",
-                LdapUserServiceBeanDefinitionParser.parseUserDetailsClass(elt, parserContext));
+                LdapUserServiceBeanDefinitionParser.parseUserDetailsClassOrUserMapperRef(elt, parserContext));
         parserContext.getRegistry().registerBeanDefinition(BeanIds.LDAP_AUTHENTICATION_PROVIDER, ldapProvider.getBeanDefinition());
 
         ConfigUtils.addAuthenticationProvider(parserContext, BeanIds.LDAP_AUTHENTICATION_PROVIDER);

+ 25 - 5
config/src/main/java/org/springframework/security/config/LdapUserServiceBeanDefinitionParser.java

@@ -1,5 +1,6 @@
 package org.springframework.security.config;
 
+import org.springframework.beans.BeanMetadataElement;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -29,6 +30,7 @@ public class LdapUserServiceBeanDefinitionParser extends AbstractUserDetailsServ
 
     static final String ATT_ROLE_PREFIX = "role-prefix";
     static final String ATT_USER_CLASS = "user-details-class";
+    static final String ATT_USER_CONTEXT_MAPPER_REF = "user-context-mapper-ref";
     static final String OPT_PERSON = "person";
     static final String OPT_INETORGPERSON = "inetOrgPerson";
 
@@ -49,8 +51,9 @@ public class LdapUserServiceBeanDefinitionParser extends AbstractUserDetailsServ
         }
 
         builder.addConstructorArgValue(parseSearchBean(elt, parserContext));
+        builder.getRawBeanDefinition().setSource(parserContext.extractSource(elt));
         builder.addConstructorArgValue(parseAuthoritiesPopulator(elt, parserContext));
-        builder.addPropertyValue("userDetailsMapper", parseUserDetailsClass(elt, parserContext));
+        builder.addPropertyValue("userDetailsMapper", parseUserDetailsClassOrUserMapperRef(elt, parserContext));
     }
 
     static RootBeanDefinition parseSearchBean(Element elt, ParserContext parserContext) {
@@ -109,15 +112,32 @@ public class LdapUserServiceBeanDefinitionParser extends AbstractUserDetailsServ
         registry.registerBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR, bdb.getBeanDefinition());
     }
 
-    static RootBeanDefinition parseUserDetailsClass(Element elt, ParserContext parserContext) {
+    static BeanMetadataElement parseUserDetailsClassOrUserMapperRef(Element elt, ParserContext parserContext) {
         String userDetailsClass = elt.getAttribute(ATT_USER_CLASS);
+        String userMapperRef = elt.getAttribute(ATT_USER_CONTEXT_MAPPER_REF);
+
+        if (StringUtils.hasText(userDetailsClass) && StringUtils.hasText(userMapperRef)) {
+            parserContext.getReaderContext().error("Attributes " + ATT_USER_CLASS + " and " + ATT_USER_CONTEXT_MAPPER_REF
+                    + " cannot be used together.", parserContext.extractSource(elt));
+        }
+
+        if (StringUtils.hasText(userMapperRef)) {
+            return new RuntimeBeanReference(userMapperRef);
+        }
+
+        RootBeanDefinition mapper;
 
         if (OPT_PERSON.equals(userDetailsClass)) {
-            return new RootBeanDefinition(PERSON_MAPPER_CLASS, null, null);
+            mapper = new RootBeanDefinition(PERSON_MAPPER_CLASS, null, null);
         } else if (OPT_INETORGPERSON.equals(userDetailsClass)) {
-            return new RootBeanDefinition(INET_ORG_PERSON_MAPPER_CLASS, null, null);
+            mapper = new RootBeanDefinition(INET_ORG_PERSON_MAPPER_CLASS, null, null);
+        } else {
+            mapper = new RootBeanDefinition(LDAP_USER_MAPPER_CLASS, null, null);
         }
-        return new RootBeanDefinition(LDAP_USER_MAPPER_CLASS, null, null);
+
+        mapper.setSource(parserContext.extractSource(elt));
+
+        return mapper;
     }
 
     static RootBeanDefinition parseAuthoritiesPopulator(Element elt, ParserContext parserContext) {

+ 5 - 2
config/src/main/resources/org/springframework/security/config/spring-security-3.0.rnc

@@ -106,6 +106,9 @@ group-role-attribute-attribute =
 user-details-class-attribute = 
     ## Allows the objectClass of the user entry to be specified. If set, the framework will attempt to load standard attributes for the defined class into the returned UserDetails object
     attribute user-details-class {"person" | "inetOrgPerson"}
+user-context-mapper-attribute =
+    ## Allows explicit customization of the loaded user object by specifying a UserDetailsContextMapper bean which will be called with the context information from the user's directory entry
+    attribute user-context-mapper-ref {xsd:token}
 
 
 ldap-user-service =
@@ -128,7 +131,7 @@ ldap-us.attlist &=
 ldap-us.attlist &=
     role-prefix?
 ldap-us.attlist &=
-    user-details-class-attribute?
+    (user-details-class-attribute | user-context-mapper-attribute)?
 
 ldap-authentication-provider =
     ## Sets up an ldap authentication provider
@@ -151,7 +154,7 @@ ldap-ap.attlist &=
 ldap-ap.attlist &=
     role-prefix?
 ldap-ap.attlist &=
-    user-details-class-attribute?
+    (user-details-class-attribute | user-context-mapper-attribute)?
 
 password-compare-element =
     ## Specifies that an LDAP provider should use an LDAP compare operation of the user's password to authenticate the user

+ 62 - 43
config/src/main/resources/org/springframework/security/config/spring-security-3.0.xsd

@@ -301,6 +301,15 @@
          </xs:simpleType>
       </xs:attribute>
    </xs:attributeGroup>
+   <xs:attributeGroup name="user-context-mapper-attribute">
+      <xs:attribute name="user-context-mapper-ref" use="required" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Allows explicit customization of the loaded user object by specifying
+               a UserDetailsContextMapper bean which will be called with the context information
+               from the user's directory entry</xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+   </xs:attributeGroup>
    <xs:element name="ldap-user-service" substitutionGroup="security:any-user-service">
       <xs:complexType>
          <xs:attributeGroup ref="security:ldap-us.attlist"/>
@@ -376,6 +385,13 @@
             </xs:restriction>
          </xs:simpleType>
       </xs:attribute>
+      <xs:attribute name="user-context-mapper-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Allows explicit customization of the loaded user object by specifying
+               a UserDetailsContextMapper bean which will be called with the context information
+               from the user's directory entry</xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
    </xs:attributeGroup>
    <xs:element name="ldap-authentication-provider">
       <xs:annotation>
@@ -504,6 +520,13 @@
             </xs:restriction>
          </xs:simpleType>
       </xs:attribute>
+      <xs:attribute name="user-context-mapper-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Allows explicit customization of the loaded user object by specifying
+               a UserDetailsContextMapper bean which will be called with the context information
+               from the user's directory entry</xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
    </xs:attributeGroup>
    <xs:attributeGroup name="password-compare.attlist">
       <xs:attribute name="password-attribute" type="xs:token">
@@ -623,7 +646,16 @@
                      </xs:sequence>
                   </xs:complexType>
                </xs:element>
-               <xs:element ref="security:expression-handler"/>
+               <xs:element name="expression-handler">
+                  <xs:annotation>
+                     <xs:documentation>Defines the SecurityExpressionHandler instance which will be
+                        used if expression-based access-control is enabled. A default implementation
+                        (with no ACL support) will be used if not supplied.</xs:documentation>
+                  </xs:annotation>
+                  <xs:complexType>
+                     <xs:attributeGroup ref="security:ref"/>
+                  </xs:complexType>
+               </xs:element>
             </xs:choice>
             <xs:element minOccurs="0" maxOccurs="unbounded" name="protect-pointcut">
                <xs:annotation>
@@ -693,16 +725,6 @@
          </xs:annotation>
       </xs:attribute>
    </xs:attributeGroup>
-   <xs:element name="expression-handler">
-      <xs:annotation>
-         <xs:documentation>Defines the SecurityExpressionHandler instance which will be used if
-            expression-based access-control is enabled. A default implementation (with no ACL
-            support) will be used if not supplied.</xs:documentation>
-      </xs:annotation>
-      <xs:complexType>
-         <xs:attributeGroup ref="security:ref"/>
-      </xs:complexType>
-   </xs:element>
    <xs:element name="custom-after-invocation-provider">
       <xs:annotation>
          <xs:documentation>Used to decorate an AfterInvocationProvider to specify that it should be
@@ -759,7 +781,21 @@
                   <xs:attributeGroup ref="security:form-login.attlist"/>
                </xs:complexType>
             </xs:element>
-            <xs:element ref="security:openid-login"/>
+            <xs:element name="openid-login">
+               <xs:annotation>
+                  <xs:documentation>Sets up form login for authentication with an Open ID
+                     identity</xs:documentation>
+               </xs:annotation>
+               <xs:complexType>
+                  <xs:attributeGroup ref="security:form-login.attlist"/>
+                  <xs:attribute name="user-service-ref" type="xs:token">
+                     <xs:annotation>
+                        <xs:documentation>A reference to a user-service (or UserDetailsService bean)
+                           Id</xs:documentation>
+                     </xs:annotation>
+                  </xs:attribute>
+               </xs:complexType>
+            </xs:element>
             <xs:element name="x509">
                <xs:annotation>
                   <xs:documentation>Adds support for X.509 client authentication.</xs:documentation>
@@ -823,7 +859,12 @@
                </xs:annotation>
                <xs:complexType>
                   <xs:sequence>
-                     <xs:element maxOccurs="unbounded" ref="security:port-mapping"/>
+                     <xs:element maxOccurs="unbounded" name="port-mapping">
+                        <xs:complexType>
+                           <xs:attributeGroup ref="security:http-port"/>
+                           <xs:attributeGroup ref="security:https-port"/>
+                        </xs:complexType>
+                     </xs:element>
                   </xs:sequence>
                </xs:complexType>
             </xs:element>
@@ -1108,21 +1149,6 @@
          </xs:annotation>
       </xs:attribute>
    </xs:attributeGroup>
-   <xs:element name="openid-login">
-      <xs:annotation>
-         <xs:documentation>Sets up form login for authentication with an Open ID
-            identity</xs:documentation>
-      </xs:annotation>
-      <xs:complexType>
-         <xs:attributeGroup ref="security:form-login.attlist"/>
-         <xs:attribute name="user-service-ref" type="xs:token">
-            <xs:annotation>
-               <xs:documentation>A reference to a user-service (or UserDetailsService bean)
-                  Id</xs:documentation>
-            </xs:annotation>
-         </xs:attribute>
-      </xs:complexType>
-   </xs:element>
    <xs:element name="filter-chain-map">
       <xs:annotation>
          <xs:documentation>Used to explicitly configure a FilterChainProxy instance with a
@@ -1348,12 +1374,6 @@
          </xs:annotation>
       </xs:attribute>
    </xs:attributeGroup>
-   <xs:element name="port-mapping">
-      <xs:complexType>
-         <xs:attributeGroup ref="security:http-port"/>
-         <xs:attributeGroup ref="security:https-port"/>
-      </xs:complexType>
-   </xs:element>
    <xs:attributeGroup name="http-port">
       <xs:attribute name="http" use="required" type="xs:token"/>
    </xs:attributeGroup>
@@ -1474,7 +1494,14 @@
       </xs:annotation>
       <xs:complexType>
          <xs:sequence>
-            <xs:element minOccurs="0" maxOccurs="unbounded" ref="security:user"/>
+            <xs:element minOccurs="0" maxOccurs="unbounded" name="user">
+               <xs:annotation>
+                  <xs:documentation>Represents a user in the application.</xs:documentation>
+               </xs:annotation>
+               <xs:complexType>
+                  <xs:attributeGroup ref="security:user.attlist"/>
+               </xs:complexType>
+            </xs:element>
          </xs:sequence>
          <xs:attribute name="id" type="xs:ID">
             <xs:annotation>
@@ -1488,14 +1515,6 @@
    <xs:attributeGroup name="properties-file">
       <xs:attribute name="properties" type="xs:token"/>
    </xs:attributeGroup>
-   <xs:element name="user">
-      <xs:annotation>
-         <xs:documentation>Represents a user in the application.</xs:documentation>
-      </xs:annotation>
-      <xs:complexType>
-         <xs:attributeGroup ref="security:user.attlist"/>
-      </xs:complexType>
-   </xs:element>
    <xs:attributeGroup name="user.attlist">
       <xs:attribute name="name" use="required" type="xs:token">
          <xs:annotation>

+ 12 - 0
config/src/test/java/org/springframework/security/config/LdapUserServiceBeanDefinitionParserTests.java

@@ -141,6 +141,18 @@ public class LdapUserServiceBeanDefinitionParserTests {
         assertTrue(ben instanceof InetOrgPerson);
     }
 
+    @Test
+    public void externalContextMapperIsSupported() {
+        setContext(
+                "<ldap-server id='someServer'/>" +
+                "<ldap-user-service id='ldapUDS' user-search-filter='(uid={0})' user-context-mapper-ref='mapper'/>" +
+                "<b:bean id='mapper' class='"+ InetOrgPersonContextMapper.class.getName() +"'/>");
+
+        UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
+        UserDetails ben = uds.loadUserByUsername("ben");
+        assertTrue(ben instanceof InetOrgPerson);
+    }
+
 
     private void setContext(String context) {
         appCtx = new InMemoryXmlApplicationContext(context);