Home > grails, groovy, Single Sign On, SSO > Auto-create User Domain Object with Spring Security

Auto-create User Domain Object with Spring Security

December 12th, 2011 Leave a comment Go to comments

For those who skip straight to the last page of a book to see how it ends – See Chap 7. Events of the spring-security-core plugin documentation.

For those who like a little more detail…

I just moved our grails app from using the shiro plugin to using the spring-security plugin(s). I like shiro’s filter-based config, but all the pre-built extension modules that Burt Beckwith has put together for spring-security (LDAP, CAS, etc.) makes it much easier for us to support the range of environments in which we have to deploy.

The one feature which took me a little while to figure out was how to have our app auto-create a user domain object when it is using an external authentication source. For example, say an instance of our app is configured to authentication against an LDAP server. The app has a MyUser class that holds local settings for users like preferences, documents, etc. When a user signs in for the first time and makes it past the authentication step, we need to automatically create a MyUser instance and associate it with the LDAP username. With the shiro-based authentication, we did this in the controller method which handled the authentication itself. Spring security works a little differently and there isn’t a central, post-authentication landing point.

If your app is always deployed with the same type of authentication (e.g. always with LDAP), you could put the persistence code into a custom UserDetailsService. There are several posts on the web that discuss creating a custom UserDetails object and a corresponding service for it, so this was the first approach I looked at. Chapter 11 of the spring-security-core plugin’s user guide has info on it as well. The primary shortcoming is that you can’t chain together UserDetailsServices. You have to implement one for each form of authentication with which you want to work.

If your app must work with a variety of authentication methods, it is easier to register a listener with Spring Security. Chapter 7 of the plugin guide discusses the two ways to do this. I found that handling the AuthenticationSuccessEvent was all I needed. Since we already had a Grail’s service that handles various user-related tasks, the listener object was dirt simple:

import org.springframework.beans.factory.InitializingBean
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
import org.springframework.context.ApplicationListener
import org.springframework.security.authentication.event.AuthenticationSuccessEvent

class MyAuthenticationEventListener implements ApplicationListener<AuthenticationSuccessEvent>, InitializingBean, ApplicationContextAware {
    ApplicationContext applicationContext
    def userService

    void afterPropertiesSet() {
        userService = applicationContext.getBean('userService')
    }

    void onApplicationEvent(AuthenticationSuccessEvent e) {
        //the principal field of the source object is a UserDetails object of some form.
        //The spring-security API contract guarantees that at least the username field will be populated.
        userService.createUser(e.source.principal)
    }
}

Since the whole class really just has one line of “functional” code, I could have used the closure-based approach described in section 7.3 of the user guide. I just prefer to keep true code out of the Config.groovy file.

Then, within the UserService.createUser method, the key lines look something like this:

    def user = MyUser.findByUsername(userDetails.username)
    if (!user)
    {
        MyUser.withTransaction {status ->
            user = new MyUser(username:userDetails.username /*, set any other props you want to store locally*/)
            user.save(flush: true, failOnError:true)
        }
    }
    return user

One note of interest – without the withTransaction statement, you may get an exception stating that no hibernate session exists and one cannot be opened. The withTransaction closure wraps this up nicely for you.

Spam Protection by WP-SpamFree