Announcement Announcement Module
Collapse
No announcement yet.
Combine DAO and LDAP authentification with Spring Security Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Combine DAO and LDAP authentification with Spring Security

    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
    Code:
    <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="(&amp;(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>
    FooUserDAOImpl.java
    Code:
    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;
        }
    }
    And FooLdapUserDetailsMapper.java
    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;
        }
    }
    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.
    As you can see above I manually set:
    Code:
    SecurityContextHolder.getContext().setAuthentication(newAuthentication);
    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.

    Is this a good way to combine these informations?

    BTW: I'm using Spring Security 3.1.3

    Thanks for an answer!

  • #2
    Is there someone you can help me, please?

    Thanks,
    Oilid

    Comment


    • #3
      Create a UserDetailsService that returns an arregated object. For example, you might have something that looks like this:

      Code:
      public class CustomUserDetailsService implements UserDetailsService {
          public UserDetails loadUserByUsername(String username) {
              CustomUserDetails result = new CustomUserDetails();
              .. load attributes from database and set on the result ...
              .. load attributes from ldap and set on the result ...
              return result;
          }
      
          // User is Spring Security's User object that implements UserDetails and
          // MyUser would be your interface that your application requires this ensures
          // Spring Security has what it needs (UserDetails interface) and your application has what it needs
          // (MyUser interface) w/out either knowing about one another
          private static class CustomUserDetails extends User implements MyUser {
              private String id;
              // other custom properties and acceossors omitted
          }
      }

      Comment


      • #4
        Thank you very much, Rob!
        It could implement the UserDetailsService and CustomUser object as you have described.
        I also needed to write a custom authentication provider which extends the DaoAuthenticationProvider and uses the DAO and LDAP source for the authentication. Now everything is fine :-)

        Thank you again!

        Comment

        Working...
        X