Table of content
- AppFuse 2.1.x + Spring Security 3.x
- AppFuse 2.0.x + Spring Security 2.x
- AppFuse 1.9.4 + Acegi Security
AppFuse 2.1.x + Spring Security 3.x
With AppFuse 2.1.x comes a new version of Spring Security, exactly, the 3.0.4.RELEASE, and the working mode has changed since 2.x. This means that the latest solution to work with LDAP doesn't work.
Here you can find the new approach.
Adding dependencies to the pom.xml
In the pom.xml, add the following dependencies
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>${spring.ldap.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core-tiger</artifactId>
<version>${spring.ldap.version}</version>
</dependency>
And the corresponding variable at the end:
<spring.ldap.version>1.3.1.RELEASE</spring.ldap.version>
Configuring the security.xml file
The default userDao authentication provider
You can comment it to have only one type of authentication, or you can leave it to have a chaining auth, first LDAP and after the userDao validation.
<authentication-manager>
<authentication-provider user-service-ref="userDao">
<password-encoder ref="passwordEncoder"/>
</authentication-provider>
<authentication-provider ref="ldapAuthProvider"/>
</authentication-manager>
Add the LDAP server
<ldap-server id="ldapServer" url="ldap://localhost:389/dc=example,dc=com"; manager-dn="cn=Manager,dc=example,dc=com" manager-password="pass"/>
If you don't specify the manager-dn and manager-password the connection will
be anonymous.
The authorization with custom database provider
You need to add the following beans:
<beans:bean id="ldapAuthProvider" 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="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">
<beans:constructor-arg value=""/><!-- user-search-base -->
<beans:constructor-arg value="(uid={0})"/> <!-- user-search-filter -->
<beans:constructor-arg ref="ldapServer"/>
</beans:bean>
<beans:bean id="cppAuthoritiesUserDetailsServiceImpl" class="cat.urv.cpp.webapp.util.CppAuthoritiesUserDetailsServiceImpl">
<beans:constructor-arg ref="userDao"/>
</beans:bean>
<beans:bean id="ldapAuthoritiesPopulator"
class="org.springframework.security.ldap.authentication.UserDetailsServiceLdapAuthoritiesPopulator">
<beans:constructor-arg ref="cppAuthoritiesUserDetailsServiceImpl"/>
</beans:bean>
With the class CppAuthoritiesUserDetailsServiceImpl you indicates what role have a user. The source code of this class is:
package cat.urv.cpp.webapp.util; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import cat.urv.cpp.dao.UserDao; import cat.urv.cpp.model.User; public class CppAuthoritiesUserDetailsServiceImpl implements UserDetailsService { private final transient Log log = LogFactory.getLog(CppAuthoritiesPopulator.class); private UserDao userDao; @Autowired public CppAuthoritiesUserDetailsServiceImpl(UserDao userDao) { this.userDao = userDao; } public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { User user = (User) userDao.loadUserByUsername(username); return user; } }
In case of problems... activate the debug
One recommendation, you can configure the log4j.xml file to see what's happening in the spring security environment:
<logger name="org.springframework.security">
<level value="DEBUG"/>
</logger>
<logger name="org.springframework.ldap">
<level value="DEBUG"/>
</logger>
AppFuse 2.0.x + Spring Security 2.x
This part is taken from a thread on the AppFuse user list.
Adding dependencies to the pom.xml
In the pom.xml, add the following dependencies
<dependencies>
...
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>${spring.ldap.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core-tiger</artifactId>
<version>${spring.ldap.version}</version>
</dependency>
...
</dependencies>
And the corresponding variable at the end:
<spring.ldap.version>1.3.0.RELEASE</spring.ldap.version>
Configuring the security.xml file
The default userDao authentication provider
You can comment it to have only one type of authentication, or you can leave it to have a chaining auth, first LDAP and after the userDao validation.
<authentication-provider user-service-ref="userDao">
<password-encoder ref="passwordEncoder"/>
</authentication-provider>
Add the LDAP server
<ldap-server id="ldapServer" url="ldap://localhost:389/dc=example,dc=com"; manager-dn="cn=Manager,dc=example,dc=com" manager-password="pass"/>
If you don't specify the manager-dn and manager-password the connection will
be anonymous.
The authorization
Configure the binding procedure (how ldap will do the autentication) and the populate procedure (how ldap will do the autorization, with this configuration you need to have a cn property in the LDAP to map the correct roles inside the application).
Database authorization with a custom populator
<beans:bean id="userSearch"
class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<beans:constructor-arg index="0" value=""/>
<beans:constructor-arg index="1" value="(uid={0})"/>
<beans:constructor-arg index="2" ref="ldapServer" />
</beans:bean>
<beans:bean id="ldapAuthenticationProvider"
class="org.springframework.security.providers.ldap.LdapAuthenticationProvider" autowire="default">
<custom-authentication-provider/>
<beans:constructor-arg>
<beans:bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
<beans:constructor-arg ref="ldapServer"/>
<beans:property name="userDnPatterns">
<beans:list><beans:value>uid={0}</beans:value></beans:list>
</beans:property>
<beans:property name="userSearch" ref="userSearch"/>
</beans:bean>
</beans:constructor-arg>
<beans:constructor-arg>
<beans:bean class="cat.urv.pdd3.webapp.util.Pdd3AuthoritiesPopulator">
<beans:constructor-arg ref="userDao"/>
</beans:bean>
</beans:constructor-arg>
</beans:bean>
You can create your custom Populator, in case of you want to have the mapping logic about what role have one user.
package cat.urv.custom.webapp.util; import java.util.HashSet; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ldap.core.DirContextOperations; import org.springframework.security.GrantedAuthority; import org.springframework.security.GrantedAuthorityImpl; import org.springframework.security.ldap.LdapAuthoritiesPopulator; import cat.urv.custom.dao.UserDao; import cat.urv.custom.model.Role; import cat.urv.custom.model.User; import cat.urv.custom.service.UserManager; public class CustomAuthoritiesPopulator implements LdapAuthoritiesPopulator { private final transient Log log = LogFactory.getLog(CustomAuthoritiesPopulator.class); private UserDao userDao; @Autowired public CustomAuthoritiesPopulator(UserDao userDao) { this.userDao = userDao; } public GrantedAuthority[] getGrantedAuthorities( DirContextOperations userData, String username) { Set<GrantedAuthority> userPerms = new HashSet<GrantedAuthority>(); User user = (User) userDao.loadUserByUsername(username); Set<Role> roles = user.getRoles(); //get users permissions from service for (Role perm : roles) { userPerms.add(new GrantedAuthorityImpl(perm.getName())); } return (GrantedAuthority[]) userPerms.toArray(new GrantedAuthority[userPerms.size()] ); } }
LDAP authorization from attribute
You can also be able to authorize one user using an attribute of the LDAP repository:
<beans:bean id="ldapAuthenticationProvider"
class="org.springframework.security.providers.ldap.LdapAuthenticationProvider" autowire="default">
<custom-authentication-provider/>
<beans:constructor-arg>
<beans:bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
<beans:constructor-arg ref="ldapServer"/>
<beans:property name="userDnPatterns">
<beans:list><beans:value>uid={0}</beans:value></beans:list>
</beans:property>
<beans:property name="userSearch" ref="userSearch"/>
</beans:bean>
</beans:constructor-arg>
<beans:constructor-arg>
<beans:bean class="org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator">
<beans:constructor-arg ref="ldapServer"/>
<beans:constructor-arg value="ou=People"/>
<beans:property name="groupRoleAttribute" value="cn"/>
</beans:bean>
</beans:constructor-arg>
</beans:bean>
In case of problems... activate the debug
One recommendation, you can configure the log4j.xml file to see what's happening in the spring security environment:
<logger name="org.springframework.security">
<level value="DEBUG"/>
</logger>
<logger name="org.springframework.ldap">
<level value="DEBUG"/>
</logger>
AppFuse 1.9.4 + Acegi Security
This part is taken from a thread on the AppFuse user list.
Here's what Matt has done in the past to get LDAP working with AppFuse 1.9.4. The same concepts should be applicable to AppFuse 2.0.x.
1. Change the "authenticationManager" bean to use "ldapProvider"
instead of "daoAuthenticationProvider":
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> <ref local="ldapProvider"/> <!--ref local="daoAuthenticationProvider"/--> <ref local="anonymousAuthenticationProvider"/> <ref local="rememberMeAuthenticationProvider"/> </list> </property> </bean>
2. Added ldapProvider and supporting beans:
<bean id="ldapProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider"> <constructor-arg> <bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator"> <constructor-arg ref="initialDirContextFactory"/> <property name="userDnPatterns"> <list> <value>uid={0}</value> </list> </property> <property name="userSearch" ref="userSearch"/> <property name="userDetailsMapper" ref="ldapUserDetailsMapper"/> </bean> </constructor-arg> <constructor-arg> <bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator"> <constructor-arg ref="initialDirContextFactory"/> <constructor-arg value=""/> <property name="groupRoleAttribute" value="cn"/> <property name="groupSearchFilter" value="(&(objectclass=groupOfUniqueNames)(uniqueMember={0}))"/> <property name="searchSubtree" value="true"/> <property name="rolePrefix" value=""/> <property name="convertToUpperCase" value="false"/> </bean> </constructor-arg> </bean> <bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory"> <constructor-arg value="${ldap.url}/${ldap.base}"/> <property name="managerDn" value="${ldap.username}"/> <property name="managerPassword" value="${ldap.password}"/> </bean> <bean id="userSearch" class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch"> <constructor-arg index="0" value=""/> <constructor-arg index="1" value="(uid={0})"/> <constructor-arg index="2" ref="initialDirContextFactory"/> <property name="searchSubtree" value="true"/> </bean> <bean id="ldapUserDetailsMapper" class="org.acegisecurity.userdetails.ldap.LdapUserDetailsMapper"> <property name="rolePrefix" value=""/> </bean>
3. Change the passwordEncoder bean to be LdapShaPasswordEncoder:
<bean id="passwordEncoder" class="org.acegisecurity.providers.ldap.authenticator.LdapShaPasswordEncoder"/>
In this example, my ldap.properties (which populates initialDirContextFactory) is set to:
ldap.url=ldap://localhost:1389 ldap.base=ou=system ldap.username=uid=admin,ou=system ldap.password=secret