Documentation Home

Using LDAP for Admin Security

Many orgizations use LDAP to store security credentials and wish to leverage existing LDAP infrastructure to manage Broadleaf Admin Users. Spring Security makes this relatively easy.

Steps

  • In your project, navigate to and open admin/src/main/webapp/WEB-INF/applicationContext-admin-security.xml and add the following configuration:
<bean id="blAdminUserProvisioningService" class="org.broadleafcommerce.openadmin.server.security.service.AdminUserProvisioningService">
    <property name="roleNameSubstitutions">
        <map>
            <!-- Note that you must map the LDAP role(s) to 1 or more Broadleaf
                roles -->
            <entry key="ROLE_MY_LDAP_ROLE" value="ROLE_ADMIN,ROLE_CMS"></entry>
        </map>
    </property>
</bean>
<bean id="blUserDetailsMapper"
    class="org.broadleafcommerce.openadmin.server.security.external.BroadleafAdminLdapUserDetailsMapper">
</bean>

Or add these bean definitions to your admin security configuration class:

@Bean
public AdminUserProvisioningService blAdminUserProvisioningService() {
    Map<String, String[]> roleNameSubstitutions = new HashMap<>();
    roleNameSubstitutions.put("ROLE_MY_LDAP_ROLE", new String[] {"ROLE_ADMIN", "ROLE_CMS"});

    AdminUserProvisioningService adminUserProvisioningService = new AdminUserProvisioningServiceImpl();
    adminUserProvisioningService.setRoleNameSubstitutions(roleNameSubstitutions);
    return adminUserProvisioningService;
}

@Bean
public LdapUserDetailsMapper blUserDetailsMapper() {
    return new BroadleafAdminLdapUserDetailsMapper();
}
  • The above configuration provides a custom mapping that takes the result of the authentication and allows for custom mappings to an AdminUserDetails object that works for both Spring and Broadleaf. An important function of the BroadleafAdminLdapUserDetailsMapper is that it allows you to map LDAP roles to one or more Broadleaf Admin Roles. Another important function is that the BroadleafAdminLdapUserDetailsMapper synchs the results of the mapping from LDAP into Broadleaf entities in the Broadleaf database. Each time a user logs in via LDAP, the granted roles are refreshed for that user in Broadleaf.
  • Next, you need to configure the LDAP Authentication Provider:

XML

<!-- NOTE: Refer to Spring LDAP documentation to determine how to properly configure the contextSource and ldapAuthProvider for your specific LDAP configuration. -->
<bean id="contextSource"
      class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
    <constructor-arg value="ldap://ldaphost:port"/>
    <property name="userDn" value="${some.user.dn.string}"/>
    <property name="password" value="${some.ldap.password}"/>
</bean>

<bean id="ldapAuthProvider"
      class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
    <constructor-arg>
        <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
            <constructor-arg ref="contextSource"/>
            <property name="userDnPatterns">
                <list>
                    <value>cn={0},ou=MyOU,ou=people,o=MyOrganization</value>
                    <value>cn={0},ou=MyUsers,ou=other,ou=people,o=MyOrganization</value>
                </list>
            </property>
        </bean>
    </constructor-arg>
    <constructor-arg>
        <bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
            <constructor-arg ref="contextSource"/>
            <constructor-arg value="ou=MyOU,ou=groups,o=MyOrganization"/>
            <property name="searchSubtree" value="true"/>
        </bean>
    </constructor-arg>
    <property name="userDetailsContextMapper" ref="blUserDetailsMapper"></property>
    <property name="useAuthenticationRequestCredentials" value="true"></property>
</bean>

JavaConfig

@Value("${some.user.dn.string}")
protected String userDn;

@Value("${some.ldap.password}")
protected String ldapPassword;

@Bean
public LdapContextSource contextSource() {
    LdapContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://ldaphost:port");
    contextSource.setUserDn(userDn);
    contextSource.setPassword(ldapPassword);
}

@Bean
public LdapAuthenticationProvider ldapAuthProvider(LdapContextSource ldapContextSource, LdapUserDetailsMapper userDetailsMapper) {
    BindAuthenticator bindAuthenticator = new BindAuthenticator(ldapContextSource);
    bindAuthenticator.setUserDnPatterns(new String[] {
            "cn={0},ou=MyOU,ou=people,o=MyOrganization",
            "cn={0},ou=MyUsers,ou=other,ou=people,o=MyOrganization"
    });

    DefaultLdapAuthoritiesPopulator ldapAuthoritiesPopulator = new DefaultLdapAuthoritiesPopulator(ldapContextSource, "ou=MyOU,ou=groups,o=MyOrganization");
    ldapAuthoritiesPopulator.setSearchSubtree(true);

    LdapAuthenticationProvider ldapAuthProvider = new LdapAuthenticationProvider(bindAuthenticator, ldapAuthoritiesPopulator);
    ldapAuthProvider.setUserDetailsContextMapper(userDetailsMapper);
    ldapAuthProvider.setUseAuthenticationRequestCredentials(true);
    return ldapAuthProvider;
}
  • Note that the blUserDetailsMapper is assigned to the ldapAuthProvider so that the correct mapping takes place after authentication is successful.
  • Next, remove existing (default) blAdminAuthenticationManager configuration:
<sec:authentication-manager alias="blAdminAuthenticationManager">
    <sec:authentication-provider user-service-ref="blAdminUserDetailsService">
        <sec:password-encoder ref="blAdminPasswordEncoder">
            <sec:salt-source ref="blAdminSaltSource"/>
        </sec:password-encoder>
    </sec:authentication-provider>
</sec:authentication-manager>
  • Replace the blAdminAuthenticationManager with a new configuration:
<sec:authentication-manager alias="blAdminAuthenticationManager">
    <sec:authentication-provider ref="ldapAuthProvider"/>
</sec:authentication-manager>
  • For java config change the authentication provider that gets injected to the new one that you created, replace:
@Resource(name = "blAdminAuthenticationProvider")
protected AuthenticationProvider authenticationProvider;

with:

@Resource(name = "ldapAuthProvider")
protected AuthenticationProvider authenticationProvider;

And configure the AuthenticationManagerBuilder to the use the authentication provider you created, replace:

public class AdminSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(adminUserDetailsService).passwordEncoder(passwordEncoder);
    }
}

with:

public class AdminSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }
}
  • Finally, build and deploy the admin and log in with a valid LDAP account

Additional Notes

  • The user name associated with the LDAP account will be used to create an Admin User in Broadleaf
  • NAME, LOGIN, and EMAIL are required fields in the BLC_ADMIN_USER table. NAME and EMAIL are retrieved as follows:
String email = (String) ctx.getObjectAttribute("mail");
String firstName = (String) ctx.getObjectAttribute("givenName");
String lastName = (String) ctx.getObjectAttribute("sn");
  • If the first and last names are blank, then the username is used as the name

Multi-Tenant notes

If you are using the Multi-tenant module, you'll want to extend BroadleafAdminLdapUserDetailsMapper with the following override to make sure the site_id gets populated:

protected Site determineSite(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
    return BroadleafRequestContext.getBroadleafRequestContext().getNonPersistentSite();
}