1

I have a project that will be an add-on to an existing ERP app. I've got SSO working, with a basic Spring Security setup (see my ticket here: Grails and CAS Basic Setup). Also, I'm not using the s2-quickstart.

I am still new to Java development, so please bear with me...I've been looking through documentation and examples and in some ways I'm more confused by what I've seen. I have a few questions:

1) This setup allows me access to the username of the logged in user, but not much else? How can I access the User object to access these properties? Right now, I am using:

def userDetails = springSecurityService.principal
then: userDetails ?.getUsername() to get the username
and: userDetails ?.getAuthorities() to get roles

This is all working, except is this the best way to access these properties? i.e., are there other properties in the userdetails, and how do I access those?

Perhaps the answer to this depends on hwo I go about this next bit...

2) I am authenticating with CAS, but now I want to pull some additional attributes from LDAP. I have tried creating a custom UserDetailsService like this http://grails-plugins.github.io/grails-spring-security-core/docs/manual/guide/userDetailsService.html but even after I got past initial compilation erros, it still seems to require the User domain class. Must I implement a User domain class if I want to extend UserDetails with additional values?

In my case, I really want just 1 LDAP attribute - and this all seems like a lot of lifting to get it. I have looked and looked but can't find a good working/simple example of doing this...many have Gorm involved or the s2-quickstart installed.

tia,

Bean

UPDATE

Here's my config...see error at the end:

Config.groovy snip

    grails.plugins.springsecurity.providerNames = ['casAuthenticationProvider']

grails.plugins.springsecurity.cas.active = true
grails.plugins.springsecurity.cas.sendRenew = false
grails.plugins.springsecurity.cas.serverUrlEncoding = 'UTF-8'
grails.plugins.springsecurity.cas.key = 'changeme'

grails.plugins.springsecurity.cas.loginUri = '/login'
grails.plugins.springsecurity.cas.serviceUrl = '${grails.serverURL}/j_spring_cas_security_check'
grails.plugins.springsecurity.cas.serverUrlPrefix = 'https://cas2.mydomain.com:8443/cas'
grails.plugins.springsecurity.cas.proxyCallbackUrl = '${grails.serverURL}/secure/receptor'
grails.plugins.springsecurity.cas.proxyReceptorUrl = '/secure/receptor'

grails.plugins.springsecurity.logout.afterLogoutUrl = 'https://cas2.mydomain.com:8443/cas/logout?service=' + appProtocol + '://cas2.mydomain.com:' + appPort + '/' + appName + '/'

grails.plugins.springsecurity.cas.artifactParameter = 'ticket'
grails.plugins.springsecurity.cas.serviceParameter = 'service'
grails.plugins.springsecurity.cas.filterProcessesUrl = '/j_spring_cas_security_check'
grails.plugins.springsecurity.cas.useSingleSignout = true

grails.server.loopback.url = ""

//Spring Security Core Config

grails.plugins.springsecurity.rejectIfNoRule = false // V.044::to fix redirect loop::true
grails.plugins.springsecurity.securityConfigType = "InterceptUrlMap"
/*
 * Order matters...put the most restrictive first
 */
grails.plugins.springsecurity.interceptUrlMap = [
    '/js/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
    '/css/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
    '/images/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
    '/login/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
    '/logout/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
    '/secure/receptor': ['IS_AUTHENTICATED_ANONYMOUSLY'],  // <- allows CAS to contact the receptor
    '/protected/**': ['IS_AUTHENTICATED_FULLY'],
    '/unprotected/**': ['IS_AUTHENTICATED_ANONYMOUSLY','IS_AUTHENTICATED_FULLY'],
    '/filtered/edit':      ["hasRole('ROLE_XYZ')"],
    '/filtered/create': ["authentication.uid == 'criderk'"],
    '/filtered/list': ["hasRole('ROLE_44808')"],
    '/filtered/index': ["hasRole('ROLE_44808')"],
    '/': ['IS_AUTHENTICATED_ANONYMOUSLY','IS_AUTHENTICATED_FULLY']

]


grails.plugins.springsecurity.ldap.search.attributesToReturn = ['uid','mail']

grails.plugins.springsecurity.userLookup.userDomainClassName = 'basecas_04.User'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'basecas_04.UserRole'
grails.plugins.springsecurity.authority.className = 'basecas_04.Role'

resources.groovy

// Place your Spring DSL code here
beans = {


    // load ldap roles from spring security
    initialDirContextFactory(org.springframework.security.ldap.DefaultSpringSecurityContextSource,
        "ldap://xx.xx.xx.xx:389"){
        userDn = "cn=admin,dc=mydomain,dc=com"
        password = "pw"
    }

    ldapUserSearch(org.springframework.security.ldap.search.FilterBasedLdapUserSearch,
        "ou=employees,dc=mydomain,dc=com", "uid={0}", initialDirContextFactory){

    }

    ldapAuthoritiesPopulator(org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator,
        initialDirContextFactory,"ou=groups,dc=mydomain,dc=com"){
          groupRoleAttribute = "gidNumber"
          groupSearchFilter = "memberUid={1}"
          searchSubtree = true
          rolePrefix = "ROLE_"
          convertToUpperCase = true
          ignorePartialResultException = true
    }

    ldapUserDetailsService(org.springframework.security.ldap.userdetails.LdapUserDetailsService,
    ldapUserSearch,
    ldapAuthoritiesPopulator)

    userDetailsService(basecas_04.CustomUserDetailsService)


}

CustomUserDetails.groovy

package basecas_04

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 {   

    final String mail   

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

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

    this.mail = mail
   }
}

MyUserDetailsService.groovy# (does this go in services...?...or in src/groovy?)

package basecas_04


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


import basecas_04.User

class CustomUserDetailsService implements GrailsUserDetailsService {        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 MyUserDetails(user.username, user.password, user.enabled, !user.accountExpired,                 
                !user.passwordExpired, !user.accountLocked,                 
                authorities ?: NO_ROLES, user.id, user.mail)                    }           }   }

User.groovy (domain class)

class User {

    transient springSecurityService

    String username
    String password
    String mail
    //String displayName
    boolean enabled
    boolean accountExpired
    boolean accountLocked
    boolean passwordExpired

    static constraints = {
        username blank: false, unique: true
        password blank: false
    }

    static mapping = {
        password column: '`password`'
    }

    Set<Role> getAuthorities() {
        UserRole.findAllByUser(this).collect { it.role } as Set
    }

    def beforeInsert() {
        encodePassword()
    }

    def beforeUpdate() {
        if (isDirty('password')) {
            encodePassword()
        }
    }

    protected void encodePassword() {
        password = springSecurityService.encodePassword(password)
    }
}

Error I'm getting

2014-02-18 08:37:48,106 [http-apr-8444-exec-9] ERROR errors.GrailsExceptionResolver - MissingPropertyException occurred when processing request: [GET] /basecas_04/protected/list No such property: id for class: org.springframework.security.ldap.userdetails.LdapUserDetailsImpl Possible solutions: dn. Stacktrace follows: groovy.lang.MissingPropertyException: No such property: id for class: org.springframework.security.ldap.userdetails.LdapUserDetailsImpl Possible solutions: dn at grails.plugins.springsecurity.SpringSecurityService.getCurrentUser(SpringSecurityService.groovy:80) at basecas_04.ProtectedController.list(ProtectedController.groovy:22) at grails.plugin.cache.web.filter.PageFragmentCachingFilter.doFilter(PageFragmentCachingFilter.java:195) at grails.plugin.cache.web.filter.AbstractFilter.doFilter(AbstractFilter.java:63) at org.jasig.cas.client.session.SingleSignOutFilter.doFilter(SingleSignOutFilter.java:65) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:722)

Any ideas on this:

No such property: id for class: org.springframework.security.ldap.userdetails.LdapUserDetailsImpl

Community
  • 1
  • 1
Bean
  • 550
  • 2
  • 8
  • 21
  • Glancing at your latest edits, looks like you're missing the `ldapUserDetailsService` bean. Also, you'll want your custom `UserDetailsService` under `src/groovy/` -- it's not a true Grails service. Finally, another link that may help: http://pwu-developer.blogspot.com/2012/02/grails-security-with-cas-and-ldap.html – Andrew Feb 04 '14 at 20:17
  • OK I added the ldapUserDetailsService as best I can, and moved the CustomUserDetailsService and MyUserDetails code to src/groovy...I've updated the above with my current config, but still getting errors right after logging into my CAS page. – Bean Feb 17 '14 at 17:04

2 Answers2

0

your answer probably lies in here: spring-security-ldap (if you are using this since i do believe this is what you are also using?) : https://github.com/grails-plugins/grails-spring-security-ldap/blob/master/src/java/grails/plugin/springsecurity/ldap/userdetails/GrailsLdapUserDetailsManager.java - t

Doing a quick search for :

security.ldap.userdetails.LdapUserDetailsManager

returns:

http://docs.spring.io/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.html#loadUserByUsername%28java.lang.String%29

loadUserByUsername

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException

Description copied from interface: UserDetailsService Locates the user based on the username. In the actual implementation, the search may possibly be case insensitive, or case insensitive depending on how the implementation instance is configured. In this case, the UserDetails object that comes back may have a username that is of a different case than what was actually requested..

Specified by:
    loadUserByUsername in interface UserDetailsService

Parameters:
    username - the username identifying the user whose data is required. 
Returns:
    **a fully populated user record (never null)** 
V H
  • 8,382
  • 2
  • 28
  • 48
0

It can be a bit difficult to navigate the Spring Security bean maze. On the upside, once you have your bearings, it's a very powerful and flexible bean maze :)

  • For #1, you are free to override the default UserDetails class with your own custom implementation that adds any additional properties you need. To do this, you will want to define a custom UserDetailsService as described in the core plugin documentation here.

  • For #2, this link may help: http://swordsystems.com/2011/12/21/spring-security-cas-ldap/

Andrew
  • 2,239
  • 13
  • 7
  • One thing I'm getting hung up on, in order to extend UserDetails, do I have to have a User domain class? – Bean Feb 04 '14 at 13:43
  • No, you don't necessarily need a `User` domain class. Your custom `UserDetailsService` just needs to return an object that conforms to the `UserDetails` interface. You can define and construct this `UserDetails` object however you like (ie. without relying on a `User` domain class). Burt Beckwith offers a few more tips here: https://stackoverflow.com/questions/6899566/use-existing-domain-classes-with-spring-security-plugin – Andrew Feb 04 '14 at 14:24