Created
June 9, 2014 13:33
-
-
Save ajbrown/bc163a3e4b74d91b7187 to your computer and use it in GitHub Desktop.
grails-spring-security-rest token storage service supporting token expiration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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