Search code examples
grailsspring-securitygrails-pluginspring-ldapspring-security-ldap

How to integrate an LDAP user with the PERSON table created by Spring Security in Grails?


We are creating a grails aplication where we want the user to log in using their Active Directory credentials. Additionally, we want to give the business owner of this application the ability to control who has access to certain links (actions). Because of this we are using the following plugins in our grails application:

  1. Spring Security Core
  2. Spring Security LDAP
  3. Spring Security UI

Because we want to empower the business user to create customized roles with certain permissions(actions) on the fly when necessary, we think that the best Spring Security Configuration is the Requestmap database based approach

So far we have accomplished as follows:

  • We are being able to authenticate against Active Directory successfully.
  • We have also been able to create different Request Mappings for different roles (ROLE_XXX) through the UI interface of the spring-security-ui plugin

Problems/Questions

The spring-security-core plugin created the following tables:

  • PERSON
  • AUTHORITY
  • PERSON_AUTHORITY
  • REQUESTMAP

These are the tables that support the creation of Roles, the assignment of URLs to roles. However, the Person_Authoritity table as the convention name implies it's a many to many relationship between a PERSON and an AUTHORITY (ROLE) since a person can potentially have more than one role. My problem is that I do not have a Person because the person already exists in Active Directory (an external source) and it was not created in the application.

Is there a way to make the authenticated user to be the PERSON ? The spring security solution requires that Person row or object however you prefer to refer to it.

I have also posted the question here:

http://grails.1312388.n4.nabble.com/Issues-integrating-LDAP-Authentication-with-Requestmap-to-Secure-URLs-td4644040.html

Thanks,


Solution

  • So you need to essentially map the AD user to a Person.

    Here are the 3 classes you need in src/groovy. Obviously modify them as needed:

        package yourpackagename
    
        import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser
        import org.springframework.security.core.GrantedAuthority
    
        class CustomUserDetails extends GrailsUser{
            final String firstName
            final String lastName
    
            CustomUserDetails(String username, String password, boolean enabled,
                              boolean accountNonExpired, boolean credentialsNonExpired,
                              boolean accountNonLocked,
                              Collection<GrantedAuthority> authorities,
                              long id, String firstName, String lastName) {
                super(username, password, enabled, accountNonExpired,
                        credentialsNonExpired, accountNonLocked, authorities, id)
    
                this.firstName = firstName
                this.lastName = lastName
            }
        }
    
    package yourpackagenamehere
    
    import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserDetailsService
    import org.springframework.security.core.authority.GrantedAuthorityImpl
    import org.springframework.security.core.userdetails.UserDetails
    import org.springframework.security.core.userdetails.UsernameNotFoundException
    import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
    
    class CustomUserDetailsService implements GrailsUserDetailsService {
    
        /**
         * Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least one role, so
         * we give a user with no granted roles this one which gets past that restriction but
         * doesn't grant anything.
         */
        static final List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
    
        UserDetails loadUserByUsername(String username, boolean loadRoles)
        throws UsernameNotFoundException {
            return loadUserByUsername(username)
        }
    
        UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            User.withTransaction { status ->
    
                User user = User.findByUsername(username)
                if (!user) throw new UsernameNotFoundException('User not found', username)
    
                def authorities = user.authorities.collect {new GrantedAuthorityImpl(it.authority)}
    
                return new CustomUserDetails(user.username, user.password, user.enabled,
                        !user.accountExpired, !user.passwordExpired,
                        !user.accountLocked, authorities ?: NO_ROLES, user.id,
                        user.firstName, user.lastName)
            } as UserDetails
        }
    }
    
    package yourpackagenamehere
    
    import groovy.sql.Sql
    
    import org.springframework.ldap.core.DirContextAdapter
    import org.springframework.ldap.core.DirContextOperations
    import org.springframework.security.core.userdetails.UserDetails
    import org.springframework.security.ldap.userdetails.UserDetailsContextMapper
    import org.springframework.security.core.authority.GrantedAuthorityImpl
    import org.springframework.security.core.GrantedAuthority
    import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
    
    import org.springframework.security.core.userdetails.UsernameNotFoundException
    import org.springframework.security.authentication.DisabledException
    
    class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
    
        private static final List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
    
        def dataSource
    
        @Override
        public CustomUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<GrantedAuthority> authority) {
    
            username = username.toLowerCase()
    
            User user = User.findByUsername(username)
    
            String firstName = ctx.originalAttrs.attrs['givenname'].values[0]
            String lastName = ctx.originalAttrs.attrs['sn'].values[0]
    
    
            def roles
    
            User.withTransaction {
    
                if(!user){
                    user = new User(username: username, enabled: true, firstName: firstName, lastName: lastName)
                    user.save(flush: true)
                }
                else {
                    user = User.findByUsername(username)
                    user.firstName = firstName
                    user.lastName = lastName
                    user.save(flush: true)
                }
    
                roles = user.getAuthorities()
            }
    
            if ( !user.enabled )
                throw new DisabledException("User is disabled", username)
    
    
            def authorities = roles.collect { new GrantedAuthorityImpl(it.authority) }
            authorities.addAll(authority)
            def userDetails = new CustomUserDetails(username, user.password, user.enabled, false, false, false, authorities, user.id, user.firstName, user.lastName)
    
            return userDetails
        }
    
        @Override
        public void mapUserToContext(UserDetails arg0, DirContextAdapter arg1) {
        }
    }
    

    Under configuration in spring/resources.groovy :

    import yourpackagenamehere.CustomUserDetailsService
    import yourpackagenamehere.CustomUserDetailsContextMapper
    beans = {
        userDetailsService(CustomUserDetailsService)
    
        ldapUserDetailsMapper(CustomUserDetailsContextMapper) {
            dataSource = ref("dataSource")
        }
    }
    

    Under Config.groovy, here are my settings:

    grails.plugins.springsecurity.ldap.context.managerDn = 'CN=username,OU=People,DC=foo,DC=com'
    grails.plugins.springsecurity.ldap.context.managerPassword = 'password'
    grails.plugins.springsecurity.ldap.context.server = 'ldap://foo.com:389/'
    grails.plugins.springsecurity.ldap.authorities.ignorePartialResultException = true
    grails.plugins.springsecurity.ldap.search.base = 'ou=People,dc=foo,dc=com'
    grails.plugins.springsecurity.ldap.search.filter="sAMAccountName={0}"
    grails.plugins.springsecurity.ldap.search.searchSubtree = true
    grails.plugins.springsecurity.ldap.auth.hideUserNotFoundExceptions = false
    grails.plugins.springsecurity.ldap.search.attributesToReturn = null
    grails.plugins.springsecurity.providerNames = ['ldapAuthProvider', 'anonymousAuthenticationProvider']
    grails.plugins.springsecurity.ldap.mapper.userDetailsClass = 'CustomUserDetails'
    
    grails.plugins.springsecurity.ldap.authorities.retrieveGroupRoles = true
    grails.plugins.springsecurity.ldap.authorities.retrieveDatabaseRoles = true
    grails.plugins.springsecurity.ldap.authorities.groupSearchBase ='dc=foo,dc=com'
    grails.plugins.springsecurity.ldap.authorities.groupSearchFilter = 'member={0}'