Hello guys,
I have a mysql database with login and roles tables. Additionally I have to use the LDAP to retrieve the user name and emailaddress.
My problem is now how to combine these both datasources to create a custom user object?
I tried the following configuration/implementation:
security-config.xml
FooUserDAOImpl.javaCode:<security:authentication-manager alias="authManager"> <security:authentication-provider ref="ldapProvider" /> <security:authentication-provider user-service-ref="fooUserDetailsService"> <security:password-encoder hash="sha" /> </security:authentication-provider> </security:authentication-manager> <bean id="fooUserDetailsService" class="my.company.foo.security.FooUserDAOImpl"> <property name="usersByUsernameQuery"> <value>SELECT login AS username, passwd AS password, enabled, user_id AS userId FROM users WHERE login=? AND target = 'PERSON'</value> </property> <property name="authoritiesByUsernameQuery"> <value> SELECT u.login AS username, a.authority FROM users u, authorities a, user_authorities ua WHERE u.user_id = ua.user_id AND a.authority_id = ua.authority_id AND u.enabled=1 AND u.login = ? AND u.target = 'PERSON' AND a.authority_type = 'FRONTEND' </value> </property> <property name="rolePrefix" value="" /> <property name="dataSource" ref="fooDataSource" /> </bean> <bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"> <constructor-arg value="#{appProperties.adsUrl}"/> <property name="userDn" value="#{appProperties.adsUser}" /> <property name="password" value="#{appProperties.adsPassword}" /> </bean> <bean id="ldapProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> <constructor-arg> <bean class="org.springframework.security.ldap.authentication.BindAuthenticator"> <constructor-arg ref="contextSource"/> <property name="userSearch"> <bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> <constructor-arg index="0" value=""/> <constructor-arg index="1" value="(&(objectclass=person)(!(useraccountcontrol=514))(sAMAccountName={0}))"/> <constructor-arg index="2" ref="contextSource" /> </bean> </property> </bean> </constructor-arg> <!-- roles are defined in the database --> <constructor-arg> <bean class="org.springframework.security.ldap.authentication.UserDetailsServiceLdapAuthoritiesPopulator"> <constructor-arg ref="fooUserDetailsService" /> </bean> </constructor-arg> <property name="userDetailsContextMapper"> <bean class="my.company.foo.security.FooLdapUserDetailsMapper" /> </property> </bean>
And FooLdapUserDetailsMapper.javaCode:public class FooUserDAOImpl extends JdbcDaoImpl { @Override protected List<UserDetails> loadUsersByUsername(final String username) { return this.getJdbcTemplate().query(super.getUsersByUsernameQuery(), new String[]{ username }, new RowMapper<UserDetails>() { @Override public UserDetails mapRow(final ResultSet rs, final int rowNum) throws SQLException { final String username = rs.getString(1); final String password = rs.getString(2); final boolean enabled = rs.getBoolean(3); final int userId = rs.getInt(4); return new FooUser(userId, username, password, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES); } }); } @Override protected UserDetails createUserDetails(final String username, final UserDetails userFromUserQuery, final List<GrantedAuthority> combinedAuthorities) { String returnUsername = userFromUserQuery.getUsername(); if (!this.isUsernameBasedPrimaryKey()) { returnUsername = username; } if (!(userFromUserQuery instanceof FooUser)) { throw new IllegalArgumentException("the userQuery does not return an FooUser instance"); } final int userId = ((FooUser) userFromUserQuery).getUserId(); final FooUser fooUser = new FooUser(userId, returnUsername, userFromUserQuery.getPassword(), userFromUserQuery.isEnabled(), true, true, true, combinedAuthorities); final UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(fooUser, fooUser.getPassword(), fooUser.getAuthorities()); // TODO is this a good way? SecurityContextHolder.getContext().setAuthentication(newAuthentication); return fooUser; } }
My special problem is my need to store the "user_id" into the FooUser object. It is only available on the users mysql database table and NOT in the LDAP.Code:public class FooLdapUserDetailsMapper extends LdapUserDetailsMapper { private static final String LDAP_ATTRIBUTE_MAIL = "mail"; private static final String LDAP_ATTRIBUTE_FIRST_NAME = "givenName"; private static final String LDAP_ATTRIBUTE_LAST_NAME = "sn"; @Override public UserDetails mapUserFromContext(final DirContextOperations ctx, final String username, final Collection<? extends GrantedAuthority> authorities) { UserDetails fooUser = super.mapUserFromContext(ctx, username, authorities); // get the authentification object back which was set manually before final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { final Object principal = authentication.getPrincipal(); if (principal instanceof FooUser) { fooUser = (FooUser) principal; final String firstName = (String) ctx.getObjectAttribute(LDAP_ATTRIBUTE_FIRST_NAME); final String lastName = (String) ctx.getObjectAttribute(LDAP_ATTRIBUTE_LAST_NAME); final String emailAddress = (String) ctx.getObjectAttribute(LDAP_ATTRIBUTE_MAIL); ((FooUser) principal).setEmailAddress(emailAddress); ((FooUser) principal).setLastName(lastName); ((FooUser) principal).setFirstName(firstName); } } return fooUser; } }
As you can see above I manually set:
the authentification object into the SecurityContext to be able to get it afterwards in the FooLdapUserDetailsMapper and to add the name and email address from the LDAP to the FooUser object.Code:SecurityContextHolder.getContext().setAuthentication(newAuthentication);
Is this a good way to combine these informations?
BTW: I'm using Spring Security 3.1.3
Thanks for an answer!


Reply With Quote
