Sunday, June 6, 2010

Grails Passwords, Salted

Getting authentication with Spring Security (s2) set up on Grails is nice and easy; getting your s2 passwords salted with a unique value, not so much. There's a real nice grails s2 plugin that Burt Beckwith maintains. It actually is quite well documented, but there's an awful lot of places where the code is still a ways ahead of the documentation.

So here's a quick tutorial to get your grails passwords salted:

1 Install S2

First install the spring-security plugin:

$ grails install-plugin spring-security-core

Then create your "user" and "role" domain objects. You can call the "user" and "role" classes whatever you want, and put them in whatever package you choose. I called mine cq.User and cq.Role:

$ grails s2-quickstart cq User Role

2 Basic Configuration

The s2-quickstart script will automatically add the following to your grails-app/conf/Config.groovy config file:

// Added by the Spring Security Core plugin: grails.plugins.springsecurity.userLookup.userDomainClassName = 'cq.User' grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'cq.UserRole' grails.plugins.springsecurity.authority.className = 'cq.Role'

If you know that your usernames won't change, you can use them to salt the password. While not ideal as salts (an attacker can still build out rainbow tables of common username/password combinations pretty easily), they're a lot better than no salt at all.

To use the username as a salt, all you need to do is add one config setting to your Config.groovy. Unfortunately, the s2 manual had the wrong name for this setting; this is the right setting to add to Config.groovy:

grails.plugins.springsecurity.dao.reflectionSaltSourceProperty = 'username'

I'd also recommend turning on the setting that encodes the hashed passwords as base-64 strings (instead of strings of hex digits; it'll shave a dozen characters off the size of the hashed password):

grails.plugins.springsecurity.password.encodeHashAsBase64 = true

3 Basic Password Hashing

The other thing you need to do is make sure you hash the password with the salt whenever the password is saved. The s2 quickstart tutorial directs you to do this in your user controller. Don't; you should do this in the domain models, so you don't repeat yourself.

So update your "user" class to look like this:

package cq class User { def springSecurityService String username String password boolean enabled boolean accountExpired boolean accountLocked boolean passwordExpired static mapping = { // password is a keyword in some sql dialects, so quote with backticks // password is stored as 44-char base64 hashed value password column: '`password`', length: 44 } static constraints = { username blank: false, size: 1..50, unique: true password blank: false, size: 8..100 } def beforeInsert() { encodePassword() } def beforeUpdate() { if (isDirty('password')) encodePassword() } Set getAuthorities() { UserRole.findAllByUser(this).collect { it.role } as Set } protected encodePassword() { password = springSecurityService.encodePassword(password, username) } }

The main difference between the above and what s2-quickstart generates is the internal encodePassword() method. When a new user is saved, the beforeInsert() method will be called by the gorm framework, and our user class will hash the password, with the username as a salt. When an existing user is updated, the beforeUpdate() method will be called; it will check if the password has changed, and if it has, it will also hash the new password the same way.

This way you never have to hash a user's password in a controller or other code; just pass it on through to the domain model like any other property.

4 A Quick Test

At this point you've done enough to store passwords hashed with the username as a salt. Test it out by adding some test users in your bootstrap code, and a check for authenticated users on your home page.

In grails-app/conf/BootStrap.groovy, create and save a new test user:

class BootStrap { def init = { servletContext -> new cq.User(username: 'test', enabled: true, password: 'password').save(flush: true) } def destroy = { } }

And in grails-app/views/index.gsp, add this to the top of the body:

... <body> <sec:ifLoggedIn><h1>Hey, I know you; you're <sec:username/>!</h1></sec:ifLoggedIn> <sec:ifNotLoggedIn><h1>Who are you?</h1></sec:ifNotLoggedIn> ...

Now run your app (with clean, just to make sure everything gets rebuilt properly):

$ grails clean && grails run-app

Navigate to http://localhost:8080/cq/login (where cq is the name of your app), and login with a username of test and a password of password. Pretty sweet what you get (just about) out of the box, huh?

5 Adding a Unique Salt

Let's kick it up a notch. To create a unique salt for each user (making it impractical for an attacker to use rainbow tables to crack the hashed passwords), add a salt field to your "user" class, and override the getter for this field to initialize it with a unique salt:

package cq import java.security.SecureRandom; // add class User { def springSecurityService String username String password String salt // add boolean enabled boolean accountExpired boolean accountLocked boolean passwordExpired static mapping = { // password is a keyword in some sql dialects, so quote with backticks // password is stored as 44-char base64 hashed value password column: '`password`', length: 44 } static constraints = { username blank: false, size: 1..50, unique: true password blank: false, size: 8..100 // salt is stored as 64-char base64 value salt maxSize: 64 // add } def beforeInsert() { encodePassword() } def beforeUpdate() { if (isDirty('password')) encodePassword() } // add: String getSalt() { if (!this.salt) { def rnd = new byte[48]; new SecureRandom().nextBytes(rnd) this.salt = rnd.encodeBase64() } this.salt } Set getAuthorities() { UserRole.findAllByUser(this).collect { it.role } as Set } protected encodePassword() { password = springSecurityService.encodePassword(password, salt) // update } }

Don't forget to also update the encodePassword() method to hash the password with the salt field, instead of the username field.

6 Adding Custom UserDetails

Here's where it gets tricky. S2 maintains a user class for authentication called UserDetails that's completely separate from your "user" domain model. So you have to provide a custom UserDetailsService class that creates a custom UserDetails object given a username, as well as a custom SaltSource helper-class to extract the salt value from the custom UserDetails.

The good news is that you don't have to write a whole lot of code to do this. Create the following class as src/groovy/cq/MyUserDetailsService.groovy (or with whatever namespace and classname you like):

package cq import org.codehaus.groovy.grails.plugins.springsecurity.GormUserDetailsService import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.userdetails.UserDetails class MyUserDetailsService extends GormUserDetailsService { protected UserDetails createUserDetails(user, Collection authorities) { new MyUserDetails((GrailsUser) super.createUserDetails(user, authorities), user.salt ) } }

And create the following as src/groovy/cq/MyUserDetails.groovy:

package cq import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser class MyUserDetails extends GrailsUser { public final String salt MyUserDetails(GrailsUser base, String salt) { super(base.username, base.password, base.enabled, base.accountNonExpired, base.credentialsNonExpired, base.accountNonLocked, base.authorities, base.id) this.salt = salt; } }

And create the following as src/groovy/cq/MySaltSource.groovy:

package cq import org.springframework.security.authentication.dao.ReflectionSaltSource import org.springframework.security.core.userdetails.UserDetails class MySaltSource extends ReflectionSaltSource { Object getSalt(UserDetails user) { user[userPropertyToUse] } }

Note that if you use a java UserDetails implementation, instead of a groovy implementation, you can just use ReflectionSaltSource directly — you need to customize it only to do groovy "reflection" (it does java reflection just fine).

7 Configuring UserDetails

Finally, you can configure s2 to use your custom UserDetails class by adding the following to your grails-app/conf/spring/resources.groovy:

import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH beans = { userDetailsService(cq.MyUserDetailsService) { sessionFactory = ref('sessionFactory') transactionManager = ref('transactionManager') } saltSource(cq.MySaltSource) { userPropertyToUse = CH.config.grails.plugins.springsecurity.dao.reflectionSaltSourceProperty } }

If you were to implement your UserDetails class in java you could omit the saltSource bean (since it comes configured out-of-the-box to do reflection on java classes). Otherwise, the one last piece of the puzzle is to go back and change the dao.reflectionSaltSourceProperty setting in your grails-app/conf/Config.groovy to your new salt field:

grails.plugins.springsecurity.dao.reflectionSaltSourceProperty = 'salt'

8 A Real Test

Now to verify that all this stuff is working (and will still work when you mess around with your user domain model in the future), you need some integration tests. First, let's tackle the password-hashing scheme; create a test/integration/cq/UsersTests.groovy class, and dump this in it:

package cq class UserTests extends GroovyTestCase { def springSecurityService void testPasswordIsEncodedWhenUserIsCreated() { def user = new User(username: 'testuser1', password: 'password').save(flush: true) assertEquals springSecurityService.encodePassword('password', user.salt), user.password } void testPasswordIsReEncodedWhenUserIsUpdatedWithNewPassword() { def user = new User(username: 'testuser1', password: 'password').save(flush: true) // update password user.password = 'password1' user.save(flush: true) assertEquals springSecurityService.encodePassword('password1', user.salt), user.password } void testPasswordIsNotReEncodedWhenUserIsUpdatedWithoutNewPassword() { def user = new User(username: 'testuser1', password: 'password').save(flush: true) // update user, but not password user.enabled = true user.save(flush: true) assertEquals springSecurityService.encodePassword('password', user.salt), user.password } void testPasswordIsNotReEncodedWhenUserIsReloaded() { new User(username: 'testuser1', password: 'password').save(flush: true) // reload user def user = User.findByUsername('testuser1') assertNotNull user assertEquals springSecurityService.encodePassword('password', user.salt), user.password } }

Now the authentication part. This is a bit awkward, because what we're really testing is that your custom UserDetails is implemented and configured correctly, but let's pretend that it's testing your login controller and stick it in your LoginControllerTests anyway. Create a test/integration/cq/LoginControllerTests.groovy class, and put this in it:

package cq import java.security.Principal import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; class LoginControllerTests extends GroovyTestCase { def daoAuthenticationProvider void testAuthenticationFailsWithIncorrectPassword() { def user = new User( username: 'testuser1', password: 'password', enabled: true ).save(flush: true) def token = new UsernamePasswordAuthenticationToken( new TestPrincipal('testuser1'), 'password1' ) shouldFail(BadCredentialsException) { daoAuthenticationProvider.authenticate(token) } } void testAuthenticationSucceedsWithCorrectPassword() { def user = new User( username: 'testuser1', password: 'password', enabled: true ).save(flush: true) def token = new UsernamePasswordAuthenticationToken( new TestPrincipal('testuser1'), 'password' ) def result = daoAuthenticationProvider.authenticate(token) assertTrue result.authenticated } class TestPrincipal implements Principal { String name TestPrincipal(def name) { this.name = name } boolean equals(Object o) { if (name == null) return o == null return name.equals(o) } int hashCode() { return toString().hashCode() } String toString() { return String.valueOf(name) } } }

And now run your integration tests:

$ grails test-app integration:

The console outputs only a brief overview of the results. If something went wrong, you can find the details in the target/test-reports folder; enter the following in your browser address bar for the html version of the report (where $PROJECT_HOME is the full path to your project):

file://$PROJECT_HOME/target/test-reports/html/index.html

And hey presto, you've got salt.

6 comments:

  1. Indeed, this was quite helpful, but when I got around to implementing the custom salt, I've run into some problems.

    // ----------------------------------
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userDetailsService': Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'sessionFactory' of bean class [org.example.MyUserDetailsService]: Bean property 'sessionFactory' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
    ----------------------------------


    if I change this:

    --------------------------------
    beans = {
    userDetailsService(cq.MyUserDetailsService) {
    sessionFactory = ref('sessionFactory')
    transactionManager = ref('transactionManager')
    }
    ..
    }
    --------------------------------

    to this: (comment out the refs)

    --------------------------------
    beans = {
    userDetailsService(cq.MyUserDetailsService) {
    // sessionFactory = ref('sessionFactory')
    // transactionManager = ref('transactionManager')
    }
    }
    --------------------------------


    I can compile and my bootstrap users get created and salted, (peeking at the entries in mysql) but the runtime authentication fails with a null pointer exception

    ----------------------------------------
    [http-8080-1] ERROR [/app].[default] - Servlet.service() for servlet default threw exception
    java.lang.NullPointerException: Cannot invoke method getDomainClass() on null object
    at org.codehaus.groovy.runtime.NullObject.invokeMethod(NullObject.java:77)
    ....
    at org.codehaus.groovy.grails.plugins.springsecurity.GormUserDetailsService.loadUserByUsername(GormUserDetailsService.groovy:50)
    at org.codehaus.groovy.grails.plugins.springsecurity.GormUserDetailsService$loadUserByUsername.callCurrent(Unknown Source)
    .....

    --------------------------------

    I'm wondering if there's a new/better way to include the refs in the bean definition so that they are available at runtime.


    Here's the versions of things.

    app.grails.version=1.3.7
    plugins.build-test-data=1.1.1
    plugins.fixtures=1.0.6
    plugins.hibernate=1.3.7
    plugins.spring-security-core=1.1.2
    plugins.tomcat=1.3.7

    ReplyDelete
  2. I found this:
    http://jira.grails.org/browse/GPSPRINGSECURITYOPENID-3

    which recommended:

    userDetailsService(MyUserDetailsService) {
    //sessionFactory = ref('sessionFactory')
    //transactionManager = ref('transactionManager')
    grailsApplication = ref('grailsApplication')
    }
    -----------------------

    which seems to be working just fine.

    Answering my own question

    ReplyDelete
  3. Thanks a ton. The quality of your post is unbelievable !

    ReplyDelete
  4. Excellent post. Saved me hours, thanks!

    ReplyDelete