May 14, 2013

LDAP support enhanced for CXF STS 2.7.5

I described in a previous blog how to configure the CXF STS for an LDAP directory for authentication and to retrieve user claims (attributes). The new release 2.7.5 of CXF provides extended support for roles managed in a LDAP directory. In previous versions, the LdapClaimsHandler added groups as roles if the groups were assigned to a multi-value attribute of the user. The new release provides an LdapGroupClaimsHandler which supports the case where an attribute of the groups lists the users who belong to this group. Further, it introduces the semantic of an application role. A user might have the role "User" for application X and role "Manager" and "User" for application Y.

The STS provides the semantic of an application with the AppliesTo parameter which is a URI. If you request a SAML token which includes the roles for a specific application (ex. MyApp), you get User and Manager back. A mapping is required in the STS to map the AppliesTo URI (URL or URN) to a String value like MyApp.

The sub-project Fediz provides in 1.1 (not released yet) a Maven profile to build the STS with an LDAP backend (instead of managing users/claims in a file). You can have a look at the ldap.xmlhere. The following configuration configures the LdapClaimsHandler and LdapGroupClaimsHandler. There is nothing special for the LdapClaimsHandler. The LdapGroupClaimsHandler also uses the Spring LdapContextSource and LdapTemplate.

    <util:list id="claimHandlerList">
        <ref bean="userClaimsHandler" />
        <ref bean="groupClaimsHandler" />

    <bean id="contextSource" class="">
        <property name="url" value="ldap://localhost:389/" />
        <property name="userDn" value="uid=admin,ou=system" />
        <property name="password" value="secret" />

    <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
        <constructor-arg ref="contextSource" />

    <util:map id="claimsToLdapAttributeMapping">
        <entry key=""
            value="givenName" />
        <entry key=""
            value="sn" />
        <entry key=""
            value="mail" />
        <entry key=""
            value="c" />
        <entry key=""
            value="postalCode" />
        <entry key=""
            value="postalAddress" />                        
        <entry key=""
            value="town" />
        <entry key=""
            value="st" />
        <entry key=""
            value="gender" />
        <entry key=""
            value="dateofbirth" />                                                
        <entry key=""
            value="member" />

    <bean id="userClaimsHandler" class="">
        <property name="ldapTemplate" ref="ldapTemplate" />
        <property name="claimsLdapAttributeMapping" ref="claimsToLdapAttributeMapping" />
        <property name="userBaseDN" value="ou=users,dc=fediz,dc=org" />
        <property name="userNameAttribute" value="uid" />
    <util:map id="appliesToScopeMapping">
        <entry key="urn:org:apache:cxf:fediz:fedizhelloworld"
            value="Example" />
    <bean id="groupClaimsHandler" class="">
        <property name="ldapTemplate" ref="ldapTemplate" />
        <property name="userBaseDN" value="ou=users,dc=fediz,dc=org" />
        <property name="userNameAttribute" value="uid" />
        <property name="groupBaseDN" value="ou=groups,dc=fediz,dc=org" />
        <property name="appliesToScopeMapping" ref="appliesToScopeMapping" />
    <jaxws:endpoint id="transportSTS1" implementor="#transportSTSProviderBean"
        address="/STSService" wsdlLocation="/WEB-INF/wsdl/ws-trust-1.4-service.wsdl"
        serviceName="ns1:SecurityTokenService" endpointName="ns1:TransportUT_Port">
            <entry key="ws-security.ut.validator">
                <bean class="">
                    <property name="contextName" value="LDAP" />
I've highlighted the important beans to support the mapping of groups to (application) roles. The bean LdapGroupClaimsHandler has got the following attributes:

ldapTemplateYesN.A.The Spring LDAP template
groupBaseDNYesN.A.The base group context where the search starts
groupObjectClassNogroupOfNamesObject class for groups. Used for search filter.
groupMemeberAttributeNomemberThe group attribute where the list of users are stored
groupURINo SAML attribute name where the roles should be stored
groupNameGlobalFilterNoROLEDefault uses the CN of the group as role name
groupNameScopedFilterNoSCOPE_ROLEDefault cuts the SCOPE and the underscore of the CN of the group
appliesToScopeMappingNoN.A.The mapping is required if application specific roles must be supported
userNameAttributeNocnUser id attribute. Only required if LDAP is not used for authentication and thus the DN of the user must be resolved first. Used for search filter.
userObjectClassNopersonObject class for users. Only required if LDAP is not used for authentication and thus the DN of the user must be resolved first. Used for search filter.

The bean appliesToScopeMapping defines the mapping of the URI in the AppliesTo variable to a Name as URI's are not valid within a CN of an LDAP group.

One example for the usage of groupNameScopedFilter. One more example. Let's assume you use the same LDAP directory for the application environemnt development and pre-production and defines the following naming convention for application roles:
DEV_<Application>_<ROLE>_Group and UAT_<Application>_<ROLE>_Group The groupNameScopedFilter will look like this DEV_SCOPE_ROLE_Group (assumption: Different STS instances are deployed for development and pre-production).

The following table lists a few group examples and how the role value will look like in the SAML attribute. The assumption is that the AppliesTo element is urn:org:apache:cxf:fediz:fedizhelloworld which maps to the scope Example (see configuration example above) and the groupNameScopedFilter is configured like DEV_SCOPE_ROLE_Group:

Group CNRole name

Last but not least I'd like to comment the default value of userNameAttribute which is CN. As per recommendation (5.4) the CN is typically the person's fullname and therefore doesn't fit for the user id (login name). Due to the reason that the LdapClaimsHandler had the cn as default value I wanted to keep that in sync and change it in the next non-patch release of CXF.

If you face issues or like more functionality send a message to the CXF mailing list or open a JIRA issue.

No comments:

Post a Comment