Skip to content

Instantly share code, notes, and snippets.

@ajbrown
Created June 9, 2014 13:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ajbrown/bc163a3e4b74d91b7187 to your computer and use it in GitHub Desktop.
Save ajbrown/bc163a3e4b74d91b7187 to your computer and use it in GitHub Desktop.
grails-spring-security-rest token storage service supporting token expiration
import org.apache.commons.lang.builder.HashCodeBuilder
class AuthenticationToken implements Serializable {
private static final long serialVersionUID = 20140401
String tokenValue
String username
Date dateCreated
static mapping = {
tokenValue unique: true, index: 'token_idx'
username length: 64
version false
cache true
}
static constraints = {
}
def beforeInsert() {
tokenValue = tokenValue.encodeAsSHA256()
}
boolean equals(other) {
if (!( other instanceof AuthenticationToken )) {
return false
}
other.tokenValue == tokenValue && other.username == username
}
int hashCode() {
def builder = new HashCodeBuilder()
if ( tokenValue ) builder.append( tokenValue )
if ( username ) builder.append( username )
builder.toHashCode()
}
}
import com.odobo.grails.plugin.springsecurity.rest.token.storage.TokenNotFoundException
import com.odobo.grails.plugin.springsecurity.rest.token.storage.TokenStorageService
import grails.plugin.cache.CacheEvict
import grails.plugin.cache.Cacheable
import grails.plugin.springsecurity.SpringSecurityUtils
import grails.transaction.Transactional
import groovy.time.TimeCategory
/**
* This class handles loading of AuthenticationTokens from GORM, with support for configurable token expiration.
*/
class AuthenticationTokenStorageService implements TokenStorageService {
static final CACHE_VALUE = 'auth-token'
def grailsCacheManager
def userDetailsService
/**
* Get the username for a token. Token lookups are cached, and a token that's expired based on the configuration will
* be treated like a non-existing token.
*
* @param tokenValue
* @return
* @throws TokenNotFoundException
*/
@Override
Object loadUserByToken( String tokenValue ) throws TokenNotFoundException {
def token = findTokenByPlainTextValue( tokenValue )
if( !token ) {
throw new TokenNotFoundException( "No token with the value: ${tokenValue} could be found." )
}
//A token that's past it's expiration time is the same as one that doesn't exist.
def expiryMinutes = SpringSecurityUtils.securityConfig.rest.token.expiry as Integer
if( expiryMinutes ) {
use(TimeCategory) {
if( (token.dateCreated + expiryMinutes.minutes) <= new Date() ) {
throw new TokenNotFoundException( "Token with the value: ${tokenValue} has expired." )
}
}
}
userDetailsService.loadUserByUsername( token?.username )
}
/**
* Store a token for a user, and update the token cache as necessary.
*
* @param tokenValue
* @param principal
*/
@Override
void storeToken( String tokenValue, Object principal ) {
def token = new AuthenticationToken( tokenValue: tokenValue, username: principal.username ).save()
if( token ) {
grailsCacheManager?.getCache( CACHE_VALUE )?.put( tokenValue, token )
}
}
/**
* Remove a token, and update the token cache.
*
* @param tokenValue
* @throws TokenNotFoundException
*/
@Override
@CacheEvict( value = 'auth-token', key = '#tokenValue' )
void removeToken( String tokenValue ) throws TokenNotFoundException {
def token = findTokenByPlainTextValue( tokenValue )
token?.delete( flush: true )
}
/**
* Find a token by the plainText version of it's tokenValue. AuthTokens are stored with the same level of
* encryption as user passwords, so the plainTextValue needs to be hashed using the same mechanism first.
*
* Note that the results of this are cached, and the token may not actually exist.
* @param plainTextValue
*/
@Cacheable( value = 'auth-token', key = '#plainTextValue' )
def findTokenByPlainTextValue( String plainTextValue ) {
AuthenticationToken.findByTokenValue( plainTextValue.encodeAsSHA256() )
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment