Quantcast

LDAP and CustomUserDetailsService

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

LDAP and CustomUserDetailsService

David Molloy
Hi,
    I have LDAP/Spring Security working together on my grails project and it is working fine.  However, I want to get myself into a situation where I can use 'springSecurityService.principal.getXXXX', the properties of my own User class (my Person).

I've gone down the approach of trying to implement my own MyUserDetailsService as described at:
http://burtbeckwith.github.com/grails-spring-security-core/docs/manual/guide/11%20Custom%20UserDetailsService.html

 For most purposes, it seems to work (code attached below).  On login, the 'println' statements in the code are executed.  However, in subsequent controllers, if I attempt something like:

def userDetails = springSecurityService.principal
println "Users username is "  + userDetails?.getUsername()       
println "Sample string = " + userDetails?.someString;
println "User object = " + userDetails?.myUser

I will receive a:
No such property: someString
No such property: myUser

Is there something simple I have missed or do I need to do things an entirely different way because I'm using LDAP authentication?  From the documentation, there was reference to:
"There are three options for mapping LDAP attributes to UserDetails data (as specified by the grails.plugins.springsecurity.ldap.mapper.userDetailsClass config attribute) and hopefully one of those will be sufficient for your needs. If not, it's easy to implement UserDetailsContextMapper yourself."

Unfortunately, I couldn't find out what the three options were anywhere and I failed in my attempts at the "easy" UserDetailsContextMapper :(
If this is the route I need to go down, any guidance would be greatly appreciated.

Many thanks,
David Molloy
       

-----------------------------------
package com.guru.security
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserDetailsService
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
import org.springframework.security.core.authority.GrantedAuthorityImpl
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException

class MyUserDetailsService 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 {
        println "MyUserDetailsService loadByUsername method!"
        return loadUserByUsername(username)
    }

    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User.withTransaction { status ->
            com.guru.security.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 MyUserDetails(user.username, user.password, user.enabled,
                !user.accountExpired, !user.passwordExpired,
                !user.accountLocked, authorities ?: NO_ROLES, user.id, user, "helloworld!")
        }
    }
}
----------------

package com.guru.security
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.User

class MyUserDetails extends GrailsUser {
    com.guru.security.User myUser
    String someString
   
    MyUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<GrantedAuthority> authorities,long id, com.guru.security.User myUser, String someString) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, id)
        println "MyUserDetails constructor!"
        this.myUser = myUser
        someString = someString
        }
}

------------
Resources.groovy

import com.guru.security.MyUserDetailsService

beans = {
    userDetailsService(MyUserDetailsService)       
}
-------------


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: LDAP and CustomUserDetailsService

David Molloy
Hi,
   Having done some more research, it seems that the later approach of using the UserDetailsContextMapper is specifically for mapping the LDAP attributes into the principal.  However, I'm merely trying to get the rest of my database fields into my principal object.

So, it feels like I'm doing the right thing. 

From the grails-spring-security-core docs:
"SpringSecurityService.getPrincipal() - retrieves the currently logged in user's Principal.  If authenticated, the principal will be a org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser', unless you have created a custome UserDetailsService, in which case it will be whatever implementation of UserDetails you use there"

Because I'm using the LDAP plugin, SpringSecurityService.getPrincipal() actually returns a LdapUserDetailsImpl rathern than a 'MyGrailsUser' class. 

Tried using:
def userDetails = (GrailsUser) springSecurityService.principal
but this returns a GroovyCastException:
Cannot cast object 'org.springframework.security.ldap.userdetails.LdapUserDetailsImpl: Dn: <snip>  Dn: <snip>;Username:<snip>;Password: [Protected];Enabled:true; ... etc.' with class 'org.springframework.security.ldap.userdetails.LdapUserDetailsImpl' to class'org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser'

Likewise, can't cast from LdapUserDetailsImpl to MyUserDetails

(From a coding perspective I know why this error would appear, but I'm just wondering how this type of thing can be surmounted)

From below, I know that 'MyUserDetailsService' is being called and populating the extra data in 'MyUserDetails', but how do I get to use this with an LDAP implementation?
I'm not really interested in using LDAP for anything apart from authentication (with roles and properties coming from the database)

Any help would be greatly appreciated.  I feel close but just not quite there!

Many thanks,
David





On 07/10/2010 17:34, David Molloy wrote:
Hi,
    I have LDAP/Spring Security working together on my grails project and it is working fine.  However, I want to get myself into a situation where I can use 'springSecurityService.principal.getXXXX', the properties of my own User class (my Person).

I've gone down the approach of trying to implement my own MyUserDetailsService as described at:
http://burtbeckwith.github.com/grails-spring-security-core/docs/manual/guide/11%20Custom%20UserDetailsService.html

 For most purposes, it seems to work (code attached below).  On login, the 'println' statements in the code are executed.  However, in subsequent controllers, if I attempt something like:

def userDetails = springSecurityService.principal
println "Users username is "  + userDetails?.getUsername()       
println "Sample string = " + userDetails?.someString;
println "User object = " + userDetails?.myUser

I will receive a:
No such property: someString
No such property: myUser

Is there something simple I have missed or do I need to do things an entirely different way because I'm using LDAP authentication?  From the documentation, there was reference to:
"There are three options for mapping LDAP attributes to UserDetails data (as specified by the grails.plugins.springsecurity.ldap.mapper.userDetailsClass config attribute) and hopefully one of those will be sufficient for your needs. If not, it's easy to implement UserDetailsContextMapper yourself."

Unfortunately, I couldn't find out what the three options were anywhere and I failed in my attempts at the "easy" UserDetailsContextMapper :(
If this is the route I need to go down, any guidance would be greatly appreciated.

Many thanks,
David Molloy
       

-----------------------------------
package com.guru.security
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserDetailsService
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
import org.springframework.security.core.authority.GrantedAuthorityImpl
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException

class MyUserDetailsService 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 {
        println "MyUserDetailsService loadByUsername method!"
        return loadUserByUsername(username)
    }

    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User.withTransaction { status ->
            com.guru.security.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 MyUserDetails(user.username, user.password, user.enabled,
                !user.accountExpired, !user.passwordExpired,
                !user.accountLocked, authorities ?: NO_ROLES, user.id, user, "helloworld!")
        }
    }
}
----------------

package com.guru.security
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.User

class MyUserDetails extends GrailsUser {
    com.guru.security.User myUser
    String someString
   
    MyUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<GrantedAuthority> authorities,long id, com.guru.security.User myUser, String someString) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, id)
        println "MyUserDetails constructor!"
        this.myUser = myUser
        someString = someString
        }
}

------------
Resources.groovy

import com.guru.security.MyUserDetailsService

beans = {
    userDetailsService(MyUserDetailsService)       
}
-------------


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: LDAP and CustomUserDetailsService

David Molloy
  Hi,
      Still having problems with what I'm trying to achieve.  It seems
that the code I previously posted works ok, as long as I comment out all
of my LDAP configuration lines (ie. bypass ldap completely and just do
local db password authentication).

springSecurityService.principal will return my 'MyUserDetails' class as
expected.  From this we can use the domain class.

When you enable LDAP (by uncommenting all the
grails.plugins.springsecurity.ldap entries) and use the same code:

springSecurityService.principal will return an 'LdapUserDetailsImpl'
class.  From this we cannot use the domain class

Any ideas how I can keep using the ldap plugin for authentication but
enable the domain class in my principal?
I'm really trying to avoid just throwing the domain class user into the
session if it can be helped.

Many thanks,
David




---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: LDAP and CustomUserDetailsService

David Molloy
  Hi all,
     Beating my head against the wall on this one, but might as well log
my experiences here in the hope that someone can help.

In short, I want to authenticate against LDAP but take everything else,
roles, user details etc. from the Spring Core Person class (in my case
'User'):

Depending on configuration and scenarios I have three situations now:

1) Turn off LDAP (or get LDAP password wrong and use DB Password
instead..bug/feature) my springSecurityService.principal returns a
'MyUserDetails' object, from which I can get everything I need.  
Everything except using LDAP of course.

2) Have userDetailsClass null:  log in against LDAP, but principal is
now a LdapUserDetailsImpl.  While this can give me methods like getCn()
it can't give me my domain object ('User')

3) Have userDetailsClass = 'person' : log in against LDAP, but principal
is now a ldap.userdetails.LdapUserDetailsImpl.Person object.  While
'getCn()' and a few other methods are available here, I can't get the
domain object ('User')

So in short, it seems I can get my user object without LDAP, or can have
LDAP without my User object.

Any suggestions on how I can get both?  Maybe I'm completely down the
wrong avenue here.

Many thanks in advance,
David





---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: LDAP and CustomUserDetailsService

themarchoffolly
Hi David, I'm not sure if you ever found a solution but given I been working through this and have found a solution I though it might be useful to document it here for you and for others who may come up against the same issue.

The issue in a nutshell, how to have springSecurityService.principal return a 'UserDetails' regardless of whether the login occurs using the DAOAuthenticationProvider or the LdapAuthenticationProvider.

The solution turns out to be quite simple. I'm going to assume you have both basic authentication and LDAP authentication configured with valid users for both that we can test against.

1. Create and configure your custom UserDetails, say MyUserDetails as per section 11 of the spring security core plugin user guide.

Note, at this point if you login using the basic DAO provider you will see an instance of MyUserDetails returned on springSecurityService.principal. If you login using the LDAP provider you will see an instance of an LdapUserDetailsImpl. Herein lies the problem.

2. The trick to getting the UserDetails as the principal on the LDAP login is to use a custom UserDetailsContextMapper. Your custom mapper will need to implement two methods, the most important one being mapUserFromContext(). It is worth noting that this method is declared to return a UserDetails. So all you have to do is build an instance of CustomUserDetails in this method and return it. By doing so the springSecurityService.principal will then return that instance of CustomUserDetails and not an LdapUserDetailsImpl.

3. You can then declare your custom mapper alongside your custom UserDetailsService in your resources.xml:

    userDetailsService(com.x.y.z.CustomUserDetailsService)

    ldapUserDetailsMapper(com.x.y.z.CustomLdapUserDetailsMapper) {
springSecurityService = ref("springSecurityService")
    }

Note, here I inject the springSecurityService but that may not be required for your implementation.

4. The skeleton code I've used in my CustomLdapUserDetailsMapper looks like this:

class CustomLdapUserDetailsMapper implements UserDetailsContextMapper {

    private static final List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]

    def springSecurityService

    UserDetails mapUserFromContext(DirContextOperations ctx,
  String username,
  Collection<GrantedAuthority> authority) {

User.withTransaction { status ->

            // Try and match the authenticated LDAP user to an existing database User.
   def user = User.findByUsername(username)

   if(!user) {
                 // In my case, I construct a new User in my database so I can
                 // manage state on that user just like I would a basic auth user.
         user = new User()
                 user.fillInTheBits...
                 if(!user.save(flush:true)) {
                    // handle errors on new User 
                }
            }

            // Now simply create and return an instance of CustomUserDetails
   return new CustomUserDetails(user.username, user.passwd,
user.enabled,
    !user.accountExpired,
!user.passwordExpired,
!user.accountLocked,
    authority ?: NO_ROLES,
user.id, user.displayName)
}
    }

    void mapUserToContext(UserDetails user,
 DirContextAdapter ctx) {
// unused 
    }

5. Just for completeness sake here's the code for my CustomUserDetails, the only custom field I've added is displayName, something I like to be able to reference often in my app without having to go back down to the User in database:

class CustomUserDetails extends GrailsUser {

    final String displayName

    CustomUserDetails(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
long id, String displayName) {

super(username, password, enabled, accountNonExpired,
   credentialsNonExpired, accountNonLocked, authorities, id)

this.displayName = displayName
    }
}

This approach will consistently give you the CustomUserDetails you want when accessing springSecurityService.principal across basic and LDAP logins. The code above also shows one approach for where you can plug in the logic to build a new User in your database based on a first-time LDAP authenticating user.

Hope some of this proves useful.

David

On Mon, Oct 11, 2010 at 5:25 PM, David Molloy <[hidden email]> wrote:
 Hi all,
   Beating my head against the wall on this one, but might as well log my experiences here in the hope that someone can help.

In short, I want to authenticate against LDAP but take everything else, roles, user details etc. from the Spring Core Person class (in my case 'User'):

Depending on configuration and scenarios I have three situations now:

1) Turn off LDAP (or get LDAP password wrong and use DB Password instead..bug/feature) my springSecurityService.principal returns a 'MyUserDetails' object, from which I can get everything I need.  Everything except using LDAP of course.

2) Have userDetailsClass null:  log in against LDAP, but principal is now a LdapUserDetailsImpl.  While this can give me methods like getCn() it can't give me my domain object ('User')

3) Have userDetailsClass = 'person' : log in against LDAP, but principal is now a ldap.userdetails.LdapUserDetailsImpl.Person object.  While 'getCn()' and a few other methods are available here, I can't get the domain object ('User')

So in short, it seems I can get my user object without LDAP, or can have LDAP without my User object.

Any suggestions on how I can get both?  Maybe I'm completely down the wrong avenue here.

Many thanks in advance,

David





---------------------------------------------------------------------
To unsubscribe from this list, please visit:

  http://xircles.codehaus.org/manage_email





--
"When we remember we are all mad, the mysteries disappear and life stands explained." ~Twain
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: LDAP and CustomUserDetailsService

David Molloy
In reply to this post by David Molloy
  David Russell I owe you a pint!  Many thanks for sorting this problem out!
Spent days at it, but your very useful and detailed post sorted out all
of my problems in twenty minutes.

I had made some attempts at a CustomLdapUserDetailsMapper and my own
UserDetailsService, but had mostly been trying them independently of
each other.
It all looks so simple when you see the code written in front of you
though and I certainly understand things better now.

Many thanks again for your help David,
David


On 11/10/2010 11:25, David Molloy wrote:

>  Hi all,
>     Beating my head against the wall on this one, but might as well
> log my experiences here in the hope that someone can help.
>
> In short, I want to authenticate against LDAP but take everything
> else, roles, user details etc. from the Spring Core Person class (in
> my case 'User'):
>
> Depending on configuration and scenarios I have three situations now:
>
> 1) Turn off LDAP (or get LDAP password wrong and use DB Password
> instead..bug/feature) my springSecurityService.principal returns a
> 'MyUserDetails' object, from which I can get everything I need.  
> Everything except using LDAP of course.
>
> 2) Have userDetailsClass null:  log in against LDAP, but principal is
> now a LdapUserDetailsImpl.  While this can give me methods like
> getCn() it can't give me my domain object ('User')
>
> 3) Have userDetailsClass = 'person' : log in against LDAP, but
> principal is now a ldap.userdetails.LdapUserDetailsImpl.Person
> object.  While 'getCn()' and a few other methods are available here, I
> can't get the domain object ('User')
>
> So in short, it seems I can get my user object without LDAP, or can
> have LDAP without my User object.
>
> Any suggestions on how I can get both?  Maybe I'm completely down the
> wrong avenue here.
>
> Many thanks in advance,
> David
>
>
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe from this list, please visit:
>
>    http://xircles.codehaus.org/manage_email
>
>
>

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: LDAP and CustomUserDetailsService

David Molloy
  Just as a return to this... in case anyone encounters something
similarly and stumbles across this post.

The discussion before describes how to get LDAP authentication working,
but loading the roles etc. from the database.
You can additionally pass your own properties in your 'MyUserDetails'
class with these populated upon login to LDAP via your
CustomLdapUserDetailsMapper.

One possible problem you might encounter is where you implement 'User
Switching'.  When you use User Switching you don't reauthenticate
against the LDAP server.  However, in addition, your
CustomLdapUserDetailsMapper is also not called.  This means that your
additional properties are not set.

To get around it, you need to create a similar
'CustomUserDetailsService' as described in other threads.  This will
effectively do the same as your LDAP mapper.
The slight downside of this is that normal logins call both of these
custom classes, querying the database twice  (there's probably some
workaround to this if I spent time at it).

However, you'll need both mappers to enable switching of users due to
the understandable issue above.

Anyway, just an FYI,
Kind regards,
David



On 08/11/2010 17:03, David Molloy wrote:

>  David Russell I owe you a pint!  Many thanks for sorting this problem
> out!
> Spent days at it, but your very useful and detailed post sorted out
> all of my problems in twenty minutes.
>
> I had made some attempts at a CustomLdapUserDetailsMapper and my own
> UserDetailsService, but had mostly been trying them independently of
> each other.
> It all looks so simple when you see the code written in front of you
> though and I certainly understand things better now.
>
> Many thanks again for your help David,
> David
>
>
> On 11/10/2010 11:25, David Molloy wrote:
>>  Hi all,
>>     Beating my head against the wall on this one, but might as well
>> log my experiences here in the hope that someone can help.
>>
>> In short, I want to authenticate against LDAP but take everything
>> else, roles, user details etc. from the Spring Core Person class (in
>> my case 'User'):
>>
>> Depending on configuration and scenarios I have three situations now:
>>
>> 1) Turn off LDAP (or get LDAP password wrong and use DB Password
>> instead..bug/feature) my springSecurityService.principal returns a
>> 'MyUserDetails' object, from which I can get everything I need.  
>> Everything except using LDAP of course.
>>
>> 2) Have userDetailsClass null:  log in against LDAP, but principal is
>> now a LdapUserDetailsImpl.  While this can give me methods like
>> getCn() it can't give me my domain object ('User')
>>
>> 3) Have userDetailsClass = 'person' : log in against LDAP, but
>> principal is now a ldap.userdetails.LdapUserDetailsImpl.Person
>> object.  While 'getCn()' and a few other methods are available here,
>> I can't get the domain object ('User')
>>
>> So in short, it seems I can get my user object without LDAP, or can
>> have LDAP without my User object.
>>
>> Any suggestions on how I can get both?  Maybe I'm completely down the
>> wrong avenue here.
>>
>> Many thanks in advance,
>> David
>>
>>
>>
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe from this list, please visit:
>>
>>    http://xircles.codehaus.org/manage_email
>>
>>
>>
>
> ---------------------------------------------------------------------
> To unsubscribe from this list, please visit:
>
>    http://xircles.codehaus.org/manage_email
>
>
>

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: LDAP and CustomUserDetailsService

atul
Hi,

I followed all the above steps but still not able to dispaly the username on page.
Can you please guide me to display the name. please


Atul
Loading...