(Quick Reference)

11 Password and Account Protection - Reference Documentation

Authors: Burt Beckwith, Beverley Talbott

Version: 2.0.0

11 Password and Account Protection

The sections that follow discuss approaches to protecting passwords and user accounts.

11.1 Password Hashing

By default the plugin uses the bcrypt algorithm to hash passwords. You can customize this with the grails.plugin.springsecurity.password.algorithm attribute as described below. In addition you can increase the security of your passwords by adding a salt, which can be a field of the UserDetails instance, a global static value, or any custom value you want.

bcrypt is a much more secure alternative to the message digest approaches since it supports a customizable work level which when increased takes more computation time to hash the users' passwords, but also dramatically increases the cost of brute force attacks. Given how easy it is to use GPUs to crack passwords, you should definitely consider using bcrypt for new projects and switching to it for existing projects. Note that due to the approach used by bcrypt, you cannot add an additional salt like you can with the message digest algorithms.

Enable bcrypt by using the 'bcrypt' value for the algorithm config attribute:

grails.plugin.springsecurity.password.algorithm = 'bcrypt'

and optionally changing the number of rekeying rounds (which will affect the time it takes to hash passwords), e.g.

grails.plugin.springsecurity.password.bcrypt.logrounds = 15

Note that the number of rounds must be between 4 and 31.

PBKDF2 is also supported.

The table shows configurable password hashing attributes.

If you want to use a message digest hashing algorithm, see this Java page for the available algorithms.

PropertyDefaultDescription
password.algorithm'bcrypt'passwordEncoder algorithm; 'bcrypt' to use bcrypt, 'pbkdf2' to use PBKDF2, or any message digest algorithm that is supported in your JDK
password.encodeHashAsBase64falseIf true, Base64-encode the hashed password.
password.bcrypt.logrounds10the number of rekeying rounds to use when using bcrypt
password.hash.iterations10000the number of iterations which will be executed on the hashed password/salt.

11.2 Salted Passwords

The Spring Security plugin uses hashed passwords and a digest algorithm that you specify. For enhanced protection against dictionary attacks, you should use a salt in addition to digest hashing.

Note that if you use bcrypt (the default setting) or pbkdf2, do not configure a salt (e.g. the dao.reflectionSaltSourceProperty property or a custom saltSource bean) because these algorithms use their own internally.

There are two approaches to using salted passwords in the plugin - defining a field in the UserDetails class to access by reflection, or by directly implementing SaltSource yourself.

dao.reflectionSaltSourceProperty

Set the dao.reflectionSaltSourceProperty configuration property:

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

This property belongs to the UserDetails class. By default it is an instance of grails.plugin.springsecurity.userdetails.GrailsUser, which extends the standard Spring Security User class and not your 'person' domain class. This limits the available fields unless you use a custom UserDetailsService.

As long as the username does not change, this approach works well for the salt. If you choose a property that the user can change, the user cannot log in again after changing it unless you re-hash the password with the new value. So it's best to use a property that doesn't change.

Another option is to generate a random salt when creating users and store this in the database by adding a new field to the 'person' class. This approach requires a custom UserDetailsService because you need a custom UserDetails implementation that also has a 'salt' property, but this is more flexible and works in cases where users can change their username.

SystemWideSaltSource and Custom SaltSource

Spring Security supplies a simple SaltSource implementation, SystemWideSaltSource, which uses the same salt for each user. It's less robust than using a different value for each user but still better than no salt at all.

An example override of the salt source bean using SystemWideSaltSource would look like this:

import org.springframework.security.authentication.dao.SystemWideSaltSource
beans = {
   saltSource(SystemWideSaltSource) {
      systemWideSalt = 'the_salt_value'
   }
}

To have full control over the process, you can implement the SaltSource interface and replace the plugin's implementation with your own by defining a bean in grails-app/conf/spring/resources.groovy with the name saltSource:

beans = {
   saltSource(com.foo.bar.MySaltSource) {
      // set properties
   }
}

Hashing Passwords

Regardless of the implementation, you need to be aware of what value to use for a salt when creating or updating users, for example, in a UserController's save or update action. When hashing the password, you use the two-parameter version of springSecurityService.encodePassword():

class UserController {

def springSecurityService

def save() { def userInstance = new User(params) userInstance.password = springSecurityService.encodePassword( params.password, userInstance.username) if (!userInstance.save(flush: true)) { render view: 'create', model: [userInstance: userInstance] return }

flash.message = "The user was created" redirect action: show, id: userInstance.id }

def update() { def userInstance = User.get(params.id)

if (params.password) { params.password = springSecurityService.encodePassword( params.password, userInstance.username) } userInstance.properties = params if (!userInstance.save(flush: true)) { render view: 'edit', model: [userInstance: userInstance] return }

if (springSecurityService.loggedIn && springSecurityService.principal.username == userInstance.username) { springSecurityService.reauthenticate userInstance.username }

flash.message = "The user was updated" redirect action: show, id: userInstance.id } }

If you are encoding the password in the User domain class (using beforeInsert and encodePassword) then don't call springSecurityService.encodePassword() in your controller since you'll double-hash the password and users won't be able to log in. It's best to encapsulate the password handling logic in the domain class. In newer versions of the plugin (version 1.2 and higher) code is auto-generated in the user class so you'll need to adjust that password hashing for your salt approach.

11.3 Account Locking and Forcing Password Change

Spring Security supports four ways of disabling a user account. When you attempt to log in, the UserDetailsService implementation creates an instance of UserDetails that uses these accessor methods:
  • isAccountNonExpired()
  • isAccountNonLocked()
  • isCredentialsNonExpired()
  • isEnabled()

If you use the s2-quickstart script to create a user domain class, it creates a class with corresponding properties to manage this state.

When an accessor returns true for accountExpired, accountLocked, or passwordExpired or returns false for enabled, a corresponding exception is thrown:

AccessorPropertyException
isAccountNonExpired()accountExpiredAccountExpiredException
isAccountNonLocked()accountLockedLockedException
isCredentialsNonExpired()passwordExpiredCredentialsExpiredException
isEnabled()enabledDisabledException

You can configure an exception mapping in Config.groovy to associate a URL to any or all of these exceptions to determine where to redirect after a failure, for example:

grails.plugin.springsecurity.failureHandler.exceptionMappings = [
   'org.springframework.security.authentication.LockedException':
      '/user/accountLocked',
   'org.springframework.security.authentication.DisabledException':
      '/user/accountDisabled',
   'org.springframework.security.authentication.AccountExpiredException':
      '/user/accountExpired',
   'org.springframework.security.authentication.CredentialsExpiredException':
      '/user/passwordExpired'
]

Without a mapping for a particular exception, the user is redirected to the standard login fail page (by default /login/authfail), which displays an error message from this table:

PropertyDefault
errors.login.disabled"Sorry, your account is disabled."
errors.login.expired"Sorry, your account has expired."
errors.login.passwordExpired"Sorry, your password has expired."
errors.login.locked"Sorry, your account is locked."
errors.login.fail"Sorry, we were not able to find a user with that username and password."

You can customize these messages by setting the corresponding property in Config.groovy, for example:

grails.plugin.springsecurity.errors.login.locked = "None shall pass."

You can use this functionality to manually lock a user's account or expire the password, but you can automate the process. For example, use the Quartz plugin to periodically expire everyone's password and force them to go to a page where they update it. Keep track of the date when users change their passwords and use a Quartz job to expire their passwords once the password is older than a fixed max age.

Here's an example for a password expired workflow. You'd need a simple action to display a password reset form (similar to the login form):

def passwordExpired() {
   [username: session['SPRING_SECURITY_LAST_USERNAME']]
}

and the form would look something like this:

<div id='login'>
   <div class='inner'>
      <g:if test='${flash.message}'>
      <div class='login_message'>${flash.message}</div>
      </g:if>
      <div class='fheader'>Please update your password..</div>
      <g:form action='updatePassword' id='passwordResetForm'
              class='cssform' autocomplete='off'>
         <p>
            <label for='username'>Username</label>
            <span class='text_'>${username}</span>
         </p>
         <p>
            <label for='password'>Current Password</label>
            <g:passwordField name='password' class='text_' />
         </p>
         <p>
            <label for='password'>New Password</label>
            <g:passwordField name='password_new' class='text_' />
         </p>
         <p>
            <label for='password'>New Password (again)</label>
            <g:passwordField name='password_new_2' class='text_' />
         </p>
         <p>
            <input type='submit' value='Reset' />
         </p>
      </g:form>
   </div>
</div>

It's important that you not allow the user to specify the username (it's available in the HTTP session) but that you require the current password, otherwise it would be simple to forge a password reset.

The GSP form would submit to an action like this one:

def updatePassword() {
   String username = session['SPRING_SECURITY_LAST_USERNAME']
   if (!username) {
      flash.message = 'Sorry, an error has occurred'
      redirect controller: 'login', action: 'auth'
      return
   }

String password = params.password String newPassword = params.password_new String newPassword2 = params.password_new_2 if (!password || !newPassword || !newPassword2 || newPassword != newPassword2) { flash.message = 'Please enter your current password and a valid new password' render view: 'passwordExpired', model: [username: session['SPRING_SECURITY_LAST_USERNAME']] return }

User user = User.findByUsername(username) if (!passwordEncoder.isPasswordValid(user.password, password, null /*salt*/)) { flash.message = 'Current password is incorrect' render view: 'passwordExpired', model: [username: session['SPRING_SECURITY_LAST_USERNAME']] return }

if (passwordEncoder.isPasswordValid(user.password, newPassword, null /*salt*/)) { flash.message = 'Please choose a different password from your current one' render view: 'passwordExpired', model: [username: session['SPRING_SECURITY_LAST_USERNAME']] return }

user.password = newPassword user.passwordExpired = false user.save() // if you have password constraints check them here

redirect controller: 'login', action: 'auth' }

User Cache

If the cacheUsers configuration property is set to true, Spring Security caches UserDetails instances to save trips to the database. (The default is false.) This optimization is minor, because typically only two small queries occur during login -- one to load the user, and one to load the authorities.

If you enable this feature, you must remove any cached instances after making a change that affects login. If you do not remove cached instances, even though a user's account is locked or disabled, logins succeed because the database is bypassed. By removing the cached data, you force at trip to the database to retrieve the latest updates.

Here is a sample Quartz job that demonstrates how to find and disable users with passwords that are too old:

package com.mycompany.myapp

class ExpirePasswordsJob {

static triggers = { cron name: 'myTrigger', cronExpression: '0 0 0 * * ?' // midnight daily }

def userCache

void execute() {

def users = User.executeQuery( 'from User u where u.passwordChangeDate <= :cutoffDate', [cutoffDate: new Date() - 180])

for (user in users) { // flush each separately so one failure // doesn't rollback all of the others try { user.passwordExpired = true user.save(flush: true) userCache.removeUserFromCache user.username } catch (e) { log.error "problem expiring password for user $user.username : $e.message", e } } } }