Ayant perdu quelques heures à configurer Shiro pour des mots de passe hashés en Sha512 avec salage, je partage avec vous la procédure que j'ai suivi.

Prérequis

Je suppose par la suite que vous disposez déjà d'une application Grails ou que vous en créerez une pour suivre le tutoriel qui suit.

L'ensemble a été testé avec Grails 2.3.6 et Shiro 1.2.1.

Tutoriel

Installation du plugin Shiro

Pour installer ce plugin, il vous suffit d'éditer votre fichier BuildConfig.groovy pour ajouter la dépendance suivante :

runtime ":shiro:1.2.1"

... ou de l'installer via la commande :

grails install-plugin shiro

Génération des éléments de base par Shiro

Pour bien débuter, le plugin Shiro met à notre disposition la commande Grails suivante :

grails shiro-quick-start --prefix=org.example.

... exécutez-là dans votre projet en remplaçant org.example. par le package de base dans lequel vous souhaitez que Shiro génère les fichiers dont il a besoin (n'oubliez pas le . à la fin du package !).

Dès lors, vous constaterez la création de nombreux fichiers, parmis lesquels deux classe domain définissant les User et les UserRole.

Info : par défaut, Shiro va sécuriser l'ensemble du site; si une partie doit rester publique, il vous faudra ajuster la configuration au travers de la classe SecurityFilters.

Modifier la définition d'un utilisateur Shiro

Nous souhaitons ajouter un salage sur les mots de passe utilisateurs; il faut donc les persister en ajoutant un nouvel attribut à la classe User générée par Shiro. Cette classe doit désormais correspondre à cela :

class User {
    String username
    String passwordHash
    byte[] passwordSalt
 
    static hasMany = [ roles: Role, permissions: String ]
 
    static constraints = {
        username(nullable: false, blank: false, unique: true)
    }
}

Comparaison de mots de passe

Il faut désormais pouvoir comparer ce qui est comparable : si le mot de passe d'un utilisateur est stocké en Sha512 avec salage, il faut être capable de le comparer correctement avec la saisie effectuée dans le formulaire de login.

Modifier le credentialMatcher

Pour se faire, éditez le fichier resources.groovy pour qu'il corresponde à ceci :

import org.apache.shiro.authc.credential.HashedCredentialsMatcher
import org.apache.shiro.crypto.hash.Sha512Hash;
 
// Place your Spring DSL code here
beans = {
	credentialMatcher(HashedCredentialsMatcher) {
		storedCredentialsHexEncoded = true		
		hashAlgorithmName=Sha512Hash.ALGORITHM_NAME
		hashIterations=1024
	}
}

... le bean credentialMatcher est utilisé par Shiro lors d'un login : nous ne faisons que lui indiquer la classe qu'il doit utiliser, et avec quels paramètres.

Modifier DbRealm

Le credentialMatcher ne gère pas lui-même le salage, il faut donc compléter les traitements. Cela se fait dans le fichier DbRealm.groovy où nous allons modifier la méthode authenticate qui est la méthode appelée lors d'une identification utilisateur.

Il suffit finalement de remplacer le code :

// Now check the user's password against the hashed value stored
// in the database.
def account = new SimpleAccount(username, user.passwordHash, "DbRealm")

... par celui-ci :

// Now check the user's password against the hashed and salt values stored
// in the database.
def account = new SimpleAuthenticationInfo(username, user.passwordHash, ByteSource.Util.bytes(user.passwordSalt), "DbRealm")

... la classe SimpleAuthenticationInfo nous permettant de gérer le salage associé à un mot de passe.

Tester

Pour tester l'ensemble, il vous faut créer à minima un utilisateur reconnu par Shiro.

Pour se faire, éditez le fichier BootStrap.groovy pour ajouter le code suivant à la méthode init :

// Inject user test data if no datas found
if (!User.count()) {
	def adminRole = new Role(name: 'ADMIN')
	adminRole.addToPermissions('*:*')
	adminRole.addToPermissions('admin')
	adminRole.save(flush: true, failOnError: true)
 
	// User creation with Password hash and salt
	def passwordSalt = new SecureRandomNumberGenerator().nextBytes().getBytes()
	def passwordHash = new Sha512Hash('password', passwordSalt, 1024).toHex()
	def user = new User(username: 'admin', passwordHash: passwordHash, passwordSalt: passwordSalt)
	user.addToRoles(adminRole)
	user.save(flush: true, failOnError: true)
}

... ce code se charge de créer un rôle nommé ADMIN et possédant des droits sur tout le site; il créé ensuite un utilisateur admin dont le mot de passe est password et auquel il va attribuer le rôle précédemment créé.

Vous n'avez plus qu'à démarrer votre application pour tester le formulaire avec cet utilisateur !

Conclusion

Et voilà, vous profitez désormais d'un site Grails sécurisé.

Important : pensez à créer un salage lors de chaque persistance d'un mot de passe utilisateur (création utilisateur, changement du mot de passe utilisateur); il vous suffit d'appliquer la méthode proposée dans ce billet, que l'on peut externaliser dans un service dédié pour plus de clarté. :)