How to populate LDAP credentials from Active Directory LDAP using Spring Security?

We use spring security to authenticate users from LDAP in our application. The authentication part is working correctly, but the authentication part is not working.

We cannot retrieve user roles from LDAP.

From the book "Spring Security 3" by Peter Mularien

β€œThis is because Active Directory saves group membership as attributes on the LDAP entries of the users themselves. Out of the box (at the time of publication), Spring Security does not offer LdapAuthoritiesPopulator, which can be configured to support the structure of a typical Active Directory LDAP tree.

Below is my spring-security configuration file.

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                            http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

       <http use-expressions="true" >
        <intercept-url pattern="/resources/**" filters="none" />
        <intercept-url pattern="/login" access="permitAll"/>
        <intercept-url pattern="/**" access="isAuthenticated()" />
        <form-login login-page="/login" 
                    default-target-url="/home" 
                    always-use-default-target="true"  
                    authentication-failure-url="/login?login_error=1" />
        <logout invalidate-session="true"
                logout-success-url="/"
                logout-url="/logout"/>
    </http>

    <authentication-manager alias="ldapAuthenticationManager">  
        <authentication-provider ref="ldapAuthenticationProvider"/>  
    </authentication-manager> 

    <beans:bean id="ldapAuthenticationProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">  
        <beans:constructor-arg ref="ldapBindAuthenticator"/>  
        <beans:constructor-arg ref="ldapAuthoritiesPopulator"/>  
        <beans:property name="userDetailsContextMapper" ref="ldapUserDetailsContextMapper"/>  
    </beans:bean> 

    <beans:bean id="ldapServer" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">  
        <!-- MS Active Directory -->  
        <beans:constructor-arg value="ldap://localhost:389/dc=myOrg,dc=net"/>  
        <beans:property name="userDn" value="admin"/>  
        <beans:property name="password" value="admin"/>
        <beans:property name="baseEnvironmentProperties">
            <beans:map>
                <beans:entry key="java.naming.referral" value="follow" />
            </beans:map>
        </beans:property>
    </beans:bean>  

    <beans:bean id="ldapBindAuthenticator" class="org.springframework.security.ldap.authentication.BindAuthenticator">  
        <beans:constructor-arg ref="ldapServer"/>  
        <beans:property name="userSearch" ref="ldapSearchBean"/>  
    </beans:bean>  

    <beans:bean id="ldapSearchBean" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">  
        <!-- MS Active Directory -->  
        <!-- user-search-base; relative to base of configured context source -->  
        <beans:constructor-arg value="ou=Software OU"/>  
        <!-- user-search-filter -->  
        <beans:constructor-arg value="(sAMAccountName={0})"/>  
        <beans:constructor-arg ref="ldapServer"/>  
    </beans:bean>  

    <beans:bean id="ldapAuthoritiesPopulator" class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
        <beans:constructor-arg ref="ldapServer" />
        <beans:constructor-arg value="" />
        <beans:property name="groupSearchFilter" value="(sAMAccountName={0})"/>
        <beans:property name="groupRoleAttribute" value="memberOf" />
        <beans:property name="rolePrefix" value=""/>
        <beans:property name="searchSubtree" value="true"/>
        <beans:property name="convertToUpperCase" value="false"/>
        <beans:property name="ignorePartialResultException" value="true"/>
    </beans:bean>

    <beans:bean class="org.springframework.security.ldap.userdetails.InetOrgPersonContextMapper" id="ldapUserDetailsContextMapper"/> 

</beans:beans>

Please, help.

+5
1

: https://jira.springsource.org/browse/SEC-876. , .

:

Spring XML  

<bean id="ldapUserService" class="MyUserDetailService">
  <constructor-arg ref="ldapUserSearch"/>
  <constructor-arg ref="ldapAuthoritiesPopulator"/>
</bean>
<bean id="ldapUserSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
  <constructor-arg value="OU=FOO-Accounts,OU=FOO,OU=OU-GLOBAL"/> <!-- user search base, RELATIVE TO SERVER CONTEXT (URL & base of configured LDAP server)! -->
  <constructor-arg value="(sAMAccountName={0})"/> <!-- user search filter -->
  <constructor-arg ref="ldapServer"/>
</bean>
<bean id="ldapAuthoritiesPopulator" class="MyLdapAuthoritiesPopulator">
  <constructor-arg ref="ldapServer" />
  <constructor-arg value="=OU=SomeFooBar,OU=FOO-Global-Security,OU=FOO-Groups,OU=FOO,OU=OU-GLOBAL" /> <!-- group search base, RELATIVE TO SERVER CONTEXT (URL & base of configured LDAP server)! -->
  <constructor-arg ref="roleMappings"/>
  <property name="groupRoleAttribute" value="cn" />
  <property name="groupSearchFilter" value="(member={0})" />
</bean>

Populator

, , AD, . , . , .

public class MyLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopulator {

  /**
   * Prefix assigned by Spring Security to each group/role from LDAP.
   */
  public static final String AUTHORITY_ROLE_PREFIX = "ROLE_";

  private Properties roleMappings;
  private Properties invertedRoleMappings;

  /**
   *
   * @param contextSource supplies the contexts used to search for user roles.
   * @param groupSearchBase if this is an empty string the search will be performed from the root DN
   * of the context factory. If null, no search will be performed.
   * @param roleMappings maps logical (internal) role names to names as delivered by LDAP
   */
  @SuppressWarnings("deprecation")
  public MyLdapAuthoritiesPopulator(final ContextSource contextSource,
      final String groupSearchBase,
      final Properties roleMappings) {
    super(contextSource, groupSearchBase);
    setConvertToUpperCase(false);
    setRolePrefix("");
    this.roleMappings = roleMappings;
    this.invertedRoleMappings = invertRoleMappings();
    logger.info("Processing LDAP roles based on the following mapping: {}.", roleMappings);
  }

  .....

  @Override
  public Set<GrantedAuthority> getGroupMembershipRoles(final String userDn, final String username) {
    final Set<GrantedAuthority> effectiveGroupMembershipRoles = super.getGroupMembershipRoles(
        userDn, username);
    return mapEffectiveRolesToApplicationRoles(effectiveGroupMembershipRoles);
  }

  /**
   * Maps effective LDAP roles such as 'foo_boston_dispatcher' or 'foo_boston_readonly' to
   * FOO internal roles. The internal role (i.e. the {@link GrantedAuthority}) is a combination
   * of the 'ROLE_' prefix and a {@link Role} enum value. .........
   */
  Set<GrantedAuthority> mapEffectiveRolesToApplicationRoles(final Set<GrantedAuthority> effectiveGroupMembershipRoles) {
    logger.info("Processing effective roles from LDAP: {}.", effectiveGroupMembershipRoles);
    final Set<GrantedAuthority> internalRoles = new HashSet<GrantedAuthority>();
    final List<String> effectiveRoleNames = extractRoleNamesFrom(effectiveGroupMembershipRoles);
    final List<String> unmappedGroupMembershipRoles = new ArrayList<String>();
    ......
    // in a method invoked here we do something like internalRoles.add(new GrantedAuthority(AUTHORITY_ROLE_PREFIX + role));
    ......
    logger.info("Created internal roles {}.", internalRoles);
    logger.trace(
        "The following group membership roles were not mapped to an internal equivalent: {}",
        unmappedGroupMembershipRoles);
    return internalRoles;
  }

  ......

  private List<String> extractRoleNamesFrom(final Collection<GrantedAuthority> authorities) {
    final List<String> authorityNames = new ArrayList<String>(authorities.size());
    for (GrantedAuthority authority : authorities) {
      authorityNames.add(authority.getAuthority());
    }
    return authorityNames;
  }
}
+2

All Articles