I have put some code together for a client of mine and thought I would share my experience with the Appfuse community, so here goes.
My client uses WSDL to get the fully qualified user 'CN' (LDAP speak). We then use LDAP to check that we can bind and finally use another WSDL to check that the user is authenticated to do what they want to do. An unusual set of interfaces I agree, but this is how its done. Note: you may not need to create local classes, my situation did not fit the mould provided by the acegi implementation. Note: I would not encourage verbatum copying of this work, it is very unlikely that what I needed to do will be the same for you, so this is simply to aid understanding of the building blocks. 1) It all starts with the Authentication manager, <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> <ref local="ldapProvider"/> <!--ref local="daoAuthenticationProvider"/--> <ref local="anonymousAuthenticationProvider"/> </list> </property> </bean> Notice I have removed the normal daoAuthenticationProvider and replaced it with an ldapProvider. 2) Create the ldapProvider, <bean id="clientBindAuthenticator" class="myclient.service.impl.ClientBindAuthenticator"> <property name="roleDao" ref="roleDaoPersistClient"/> <property name="userDetailsMapper" ref="ldapUserDetailsMapper"/> </bean> <bean id="clientAuthoritiesPopulator" class="myclient.service.impl.ClientAuthoritiesPopulator"> <property name="roleDaoWsdl" ref="roleDaoPersistClient"/> </bean> <bean id="ldapProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider"> <constructor-arg ref="clientBindAuthenticator"/> <constructor-arg ref="clientAuthoritiesPopulator"/> </bean> <bean id="ldapUserDetailsMapper" class="org.acegisecurity.userdetails.ldap.LdapUserDetailsMapper"> <property name="rolePrefix" value=""/> </bean> So the ldapProvider is still an LdapAuthenticationProvider but its arguments are now locally implemented classes. 3) We also need to define the WSDL beans so they can be used in the authentication an authorisation <bean id="getUserInfoConn1" class="myclient.getuserinfov1.GetUserInfoV1ServiceStub"> <constructor-arg value="http://192.24.222.105:80/Client/getUserInfoV1" /> </bean> <bean id="getGroupMembersConn1" class="myclient.getgroupmembersv1.GetGroupMembersV1ServiceStub"> <constructor-arg value="http://192.24.222.105:80/Client/getGroupMembersV1" /> </bean> <!-- UserDao: Hibernate implementation --> <bean id="roleDaoPersistClient" class="myclient.dao.persist.RoleDaoWsdl"> <property name="groupname" value="myGroup"/> <property name="adminUsername" value="user"/> <property name="adminPassword" value="password"/> <property name="getUserInfoConnectionPoolManager" ref="getUserInfoConnectionPoolManager"/> <property name="getGroupMembersConnectionPoolManager" ref="getGroupMembersConnectionPoolManager"/> </bean> <bean id="lookupDaoPersistClient" class="myclient.dao.persist.LookupDaoWsdl"> <property name="roleDao" ref="roleDaoPersistClient"/> </bean> <bean id="ldapInitialContext1" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory"> <constructor-arg value="${ldap.url}/${ldap.base}"/> <property name="extraEnvVars"> <map> <entry key="com.sun.jndi.ldap.connect.timeout" value="150"></entry> </map> </property> </bean> So our role class will now use wsdl calls to get information about the clients CN and allowed roles. I used Axis2 to create all of the Java code for the WSDL's and then simply created beans around them. The last bean defines the ldap initial context which we will use to make the LDAP call. 4) Create the local controlling code. The ClientBindAuthenticator is called, here is my code public class ClientBindAuthenticator implements LdapAuthenticator, InitializingBean, MessageSourceAware { private RoleDao roleDao = null; private ConnectionPoolManager mConnectionPoolManager = null; public ConnectionPoolManager getConnectionPoolManager() { return mConnectionPoolManager; } public void setConnectionPoolManager(ConnectionPoolManager pConnectionPoolManager) { mConnectionPoolManager = pConnectionPoolManager; } public RoleDao getRoleDao() { return roleDao; } public void setRoleDao(RoleDao roleDao) { this.roleDao = roleDao; } public ClientBindAuthenticator(){ } public String[] getUserAttributes(){ return null; } private LdapEntryMapper mUserDetailsMapper; public LdapEntryMapper getUserDetailsMapper(){ return mUserDetailsMapper; } public void setUserDetailsMapper(LdapEntryMapper pUserDetailsMapper){ this.mUserDetailsMapper = pUserDetailsMapper; } public LdapUserDetails authenticate(String pUsername, String pPassword) { LdapUserDetails ldapUserDetails = null; try { QueryResponse qr = getRoleDao().getUserQueryResponse(pUsername); if ( ( qr != null ) && ( "found".equals(qr.getResult()) ) && canBindWithDn(qr.getDn(), pPassword) ){ LdapUserDetailsImpl.Essence ldapUserDetailsImplEssence = new LdapUserDetailsImpl.Essence(); ldapUserDetailsImplEssence.setAccountNonExpired(!qr.isAccountExpired()); ldapUserDetailsImplEssence.setAccountNonLocked(!qr.isAccountDissabled()); ldapUserDetailsImplEssence.setCredentialsNonExpired(!qr.isAccountExpired()); ldapUserDetailsImplEssence.setDn(qr.getDn()); ldapUserDetailsImplEssence.setEnabled(!qr.isAccountDissabled()); ldapUserDetailsImplEssence.setPassword(pPassword); ldapUserDetailsImplEssence.setUsername(pUsername); ldapUserDetails = ldapUserDetailsImplEssence.createUserDetails(); } } catch ( Throwable e ){ e.printStackTrace(); throw new BadCredentialsException("Could not deturmine the validity of your credencials"); } if ( ldapUserDetails == null ){ throw new BadCredentialsException("The Credentials you have entered are not valid for use of the optimise system"); } return ldapUserDetails; //To change body of implemented methods use File | Settings | File Templates. } public boolean canBindWithDn(String userDn, String password) { this.getConnectionPoolManager().resetPoolParticipantsIterator(); DefaultInitialDirContextFactory poolParticipant = null; boolean succeeded = false; boolean badCreds = false; Date now = new Date(); while ( ( ! succeeded ) && ( ! badCreds ) && ( poolParticipant = (DefaultInitialDirContextFactory)this.getConnectionPoolManager().getNextPoolParticipant() ) != null ){ try { poolParticipant.setManagerDn(userDn); poolParticipant.setManagerPassword(password); // this call blows up if the user is no good System.out.println("before bind call"); DirContext dirContext = poolParticipant.newInitialDirContext(); System.out.println("after bind call, took : " + (new Date().getTime() - now.getTime()) + "ms"); dirContext.close(); this.getConnectionPoolManager().setPoolParticipantIsUp(poolParticipant, true); succeeded = true; } catch ( BadCredentialsException bad ){ this.getConnectionPoolManager().setPoolParticipantIsUp(poolParticipant, true); badCreds = true; } catch (Throwable e) { // This will be thrown if an invalid user name is used and the method may // be called multiple times to try different names, so we trap the exception // unless a subclass wishes to implement more specialized behaviour. this.getConnectionPoolManager().setPoolParticipantIsUp(poolParticipant, false); System.out.println("Threw error in bind : " + e); System.out.println("after failed bind call, took : " + (new Date().getTime() - now.getTime()) + "ms"); } } return succeeded; } public void afterPropertiesSet() throws Exception { //To change body of implemented methods use File | Settings | File Templates. } public void setMessageSource(MessageSource messageSource) { //To change body of implemented methods use File | Settings | File Templates. } } So in the main call to authenticate a call is made to the role dao to use the wsdl to get the users CN. A method called canBindWithDn is then called with this CN and password. I found the best way to understand all the security stuff was to actually find the code and look at it, when you do you will probably be suprised how gracefully simple it is. (Try to ignore the added complexity of the connection pool manager I had to add) The LDAP call gets a DefaultInitialDirConnectionFactory (defined as a bean), sets the DN and password and attempts to bind. If for any reason it cannot(account dissabled etc) the bind will fail. Thats all I need to know from this call, are they authenticated, yes! Finally, if the bind was a success the ldapUserDetailsImpl.Essence is filled in to pass back to the client. That information will be stored in the session for reference(again look at the code and the magic will become mere trickery) 5) The Role WSDL work, have a look at the code, public class RoleDaoWsdl implements RoleDao { private ConnectionPoolManager mGetUserInfoConnectionPoolManager; private ConnectionPoolManager mGetGroupMembersConnectionPoolManager; private String mAdminUsername; private String mAdminPassword; public ConnectionPoolManager getGetUserInfoConnectionPoolManager() { return mGetUserInfoConnectionPoolManager; } public void setGetUserInfoConnectionPoolManager(ConnectionPoolManager pGetUserInfoConnectionPoolManager) { mGetUserInfoConnectionPoolManager = pGetUserInfoConnectionPoolManager; } public ConnectionPoolManager getGetGroupMembersConnectionPoolManager() { return mGetGroupMembersConnectionPoolManager; } public void setGetGroupMembersConnectionPoolManager(ConnectionPoolManager pGetGroupMembersConnectionPoolManager) { mGetGroupMembersConnectionPoolManager = pGetGroupMembersConnectionPoolManager; } public String getAdminUsername() { return mAdminUsername; } public void setAdminUsername(String pAdminUsername) { this.mAdminUsername = pAdminUsername; } public String getAdminPassword() { return mAdminPassword; } public void setAdminPassword(String pAdminPassword) { this.mAdminPassword = pAdminPassword; } private String mGroupname = null; public String getGroupname() { return mGroupname; } public void setGroupname(String pGroupname) { this.mGroupname = pGroupname; } public myclient.getuserinfov1.internal.response.QueryResponse getUserQueryResponse(String pUsername) throws UsernameNotFoundException { myclient.getuserinfov1.internal.response.QueryResponse qr = null; try { System.out.println("Going to call getUserInfo with username : " + pUsername); GetUserInfo getUserInfo = new GetUserInfo(); getUserInfo.setSvc_acct(getAdminUsername()); getUserInfo.setSvc_pwd(getAdminPassword()); getUserInfo.setUserid(pUsername); this.getGetUserInfoConnectionPoolManager().resetPoolParticipantsIterator(); boolean success = false; Date start = new Date(); GetUserInfoV1ServiceStub pooledUserInfoService = null; while ( ( ! success ) && ( ( pooledUserInfoService = (GetUserInfoV1ServiceStub)this.getGetUserInfoConnectionPoolManager().getNextPoolParticipant() ) != null ) ){ try { GetUserInfoCallbackHandler callbackHandler = new GetUserInfoCallbackHandler(pooledUserInfoService, this.getGetUserInfoConnectionPoolManager().getTimeout()); qr = callbackHandler.makeBlockingCallToGetResults(getUserInfo); System.out.println("Completed in : " + (new Date().getTime() - start.getTime()) + "ms" ); this.getGetUserInfoConnectionPoolManager().setPoolParticipantIsUp(pooledUserInfoService, true); // got a result regardless of good or bad creds success = true; boolean badCreds = "found".equals(qr.getResult()); System.out.println("Finished call to getUserInfo : " + ( badCreds ? "Successfully" : "Bad Credencials")); String[] attr = qr.getAttrWithName("memberOf"); if ( ( attr != null ) && ( attr.length > 0 ) ){ for (int i=0; i<attr.length;i++){ System.out.println("group : " + attr[i]); } } else { System.out.println("group attr not returned"); } } catch ( Throwable ee ){ System.out.println("Failed in : " + (new Date().getTime() - start.getTime()) + "ms" ); this.getGetUserInfoConnectionPoolManager().setPoolParticipantIsUp(pooledUserInfoService, false); continue; } finally { pooledUserInfoService.cleanup(); } } } catch ( Throwable e ){ throw new UsernameNotFoundException("Failed to get user QueryResponse",e); } return qr; } // This will currently only pass back one role ever as it is just looking for one groupname private Set doRolesByUsernameCall(String pUsername) throws UsernameNotFoundException { Set retSet = new HashSet(); try { GetGroupMembers groupMembers = new GetGroupMembers(); groupMembers.setSvc_acct(getAdminUsername()); groupMembers.setSvc_pwd(getAdminPassword()); groupMembers.setGroup(getGroupname()); System.out.println("Groupname looking for is : " + getGroupname()); this.getGetGroupMembersConnectionPoolManager().resetPoolParticipantsIterator(); boolean success = false; QueryResponse qr = null; Date start = new Date(); GetGroupMembersV1ServiceStub pooledGroupMembersService = null; while ( ( ! success ) && ( ( pooledGroupMembersService = (GetGroupMembersV1ServiceStub)this.getGetGroupMembersConnectionPoolManager().getNextPoolParticipant() ) != null ) ){ try { GetGroupMembersCallbackHandler callbackHandler = new GetGroupMembersCallbackHandler(pooledGroupMembersService, this.getGetGroupMembersConnectionPoolManager().getTimeout()); pooledGroupMembersService.startgetGroupMembersV1Operation(groupMembers,callbackHandler); qr = callbackHandler.makeBlockingCallToGetResults(groupMembers); System.out.println("Completed in : " + (new Date().getTime() - start.getTime()) + "ms" ); this.getGetGroupMembersConnectionPoolManager().setPoolParticipantIsUp(pooledGroupMembersService, true); Set validUsers = getUsersInGroup(qr); System.out.println("Got a return set with size " + (validUsers != null ? validUsers.size() + "" : "empty set")); if ( ( validUsers != null ) && ( validUsers.size() > 0 ) ){ Iterator validUsersIter = validUsers.iterator(); while (validUsersIter.hasNext() ){ String validUser = (String)validUsersIter.next(); System.out.println("group role : " + validUser ); if ( validUser.equalsIgnoreCase(pUsername) ){ System.out.println("Got the match, allocating role"); retSet.add(TestModelHelper.getTestRole()); break; } } } else { System.out.println("group role attr not returned"); } success = true; } catch ( Throwable ee ){ System.out.println("Failed in : " + (new Date().getTime() - start.getTime()) + "ms" ); this.getGetGroupMembersConnectionPoolManager().setPoolParticipantIsUp(pooledGroupMembersService, false); continue; } finally { pooledGroupMembersService.cleanup(); } } } catch ( Throwable e ){ e.printStackTrace(); throw new UsernameNotFoundException("Failed call to getGroupMembers",e); } return retSet; } private Set getUsersInGroup(QueryResponse qr) { Set retSet = new HashSet(); if ( ( qr != null ) && ( qr.getMembers() != null ) && ( qr.getMembers().getValue() != null ) ){ Members_type2 members = qr.getMembers(); Value_type1[] values = members.getValue(); for (int i=0; i<values.length;i++){ Value_type1 value = values[i]; retSet.add(value.getUserPrincipalName()); } } return retSet; } public Set getRolesByUsername(String pUsername) throws UsernameNotFoundException { return this.doRolesByUsernameCall(pUsername); } public List getRoles(Role role) { //return getHibernateTemplate().find("from Role"); return TestModelHelper.getRoles(); } public Role getRole(Long roleId) { //return (Role) getHibernateTemplate().get(Role.class, roleId); throw new RuntimeException("Method not supported"); } public Role getRoleByName(String rolename) { /**List roles = getHibernateTemplate().find("from Role where name=?", rolename); if (roles.isEmpty()) { return null; } else { return (Role) roles.get(0); }**/ throw new RuntimeException("Method not supported"); } public void saveRole(Role role) { throw new RuntimeException("Method not supported"); //getHibernateTemplate().saveOrUpdate(role); } public void removeRole(String rolename) { //Object role = getRoleByName(rolename); //getHibernateTemplate().delete(role); } public List getObjects(Class clazz) { return getRoles(null); } public Object getObject(Class clazz, Serializable id) { throw new RuntimeException("Method not supported"); } public void saveObject(Object o) { //To change body of implemented methods use File | Settings | File Templates. } public void removeObject(Class clazz, Serializable id) { throw new RuntimeException("Method not supported"); //To change body of implemented methods use File | Settings | File Templates. } } As can be seen from this over complex file, is that the role information is got from calling the two WSDL beans. One gets the user information and the other gets the users in that group(not very efficient, but it works). Thats it! I think it makes sence. Well to someone who has been wandering around this code for several days it does anyway. I was not going completely for elegance I was going for delivery, as is the way of things; but it worked for me. Feel free to comment, but remember I am not the great and powerful Matt Raible. Long live Appfuse :) Thanks Nigel -- View this message in context: http://www.nabble.com/Security-with-LDAP-and-WSDL-tf3887233s2369.html#a11018942 Sent from the AppFuse - User mailing list archive at Nabble.com. --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]