We are using spring security to authenticate users from LDAP in our application. The authentication part is working properly but the authorization part is not working.
We are not able to retrieve the roles of the user from the LDAP.
From the book "Spring Security 3" by Peter Mularien
"This is because Active Directory stores group membership as attributes on the LDAP entries of users themselves. Out of the box (as of the time of publishing), Spring Security does not offer an LdapAuthoritiesPopulator that 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.
You might want to take a look here: https://jira.springsource.org/browse/SEC-876. Although this code contribution was declined, with a reasonable answer, it might give you hints.
We use the following config:
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
There's a lot of proprietary code I cannot share because our customer has extra information in the AD we need to extract. I removed that as its of no concern for the question. Hence, this code won't compile.
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With