(Quick Reference)

4 Required and Optional Domain Classes - Reference Documentation

Authors: Burt Beckwith, Beverley Talbott

Version: 1.2.7.3

4 Required and Optional Domain Classes

By default the plugin uses regular Grails domain classes to access its required data. It's easy to create your own user lookup code though, which can access the database or any other source to retrieve user and authority data. See Custom UserDetailsService for how to implement this.

To use the standard user lookup you'll need at a minimum a 'person' and an 'authority' domain class. In addition, if you want to store URL<->Role mappings in the database (this is one of multiple approaches for defining the mappings) you need a 'requestmap' domain class. If you use the recommended approach for mapping the many-to-many relationship between 'person' and 'authority,' you also need a domain class to map the join table.

The s2-quickstart script creates initial domain classes for you. You specify the package and class names, and it creates the corresponding domain classes. After that you can customize them as you like. You can add unlimited fields, methods, and so on, as long as the core security-related functionality remains.

4.1 Person Class

Spring Security uses an Authentication object to determine whether the current user has the right to perform a secured action, such as accessing a URL, manipulating a secured domain object, accessing a secured method, and so on. This object is created during login. Typically overlap occurs between the need for authentication data and the need to represent a user in the application in ways that are unrelated to security. The mechanism for populating the authentication is completely pluggable in Spring Security; you only need to provide an implementation of UserDetailsService and implement its one method, loadUserByUsername().

By default the plugin uses a Grails 'person' domain class to manage this data. The class name is Person, and username, enabled, password are the default names of the required properties. You can easily plug in your own implementation, and rename the class, package, and fields. In addition, you should define an authorities property to retrieve roles; this can be a public field or a getAuthorities() method, and it can be defined through a traditional GORM many-to-many or a custom mapping.

Assuming you choose com.mycompany.myapp as your package, and User as your class name, you'll generate this class:

package com.mycompany.myapp

class User {

String username String password boolean enabled boolean accountExpired boolean accountLocked boolean passwordExpired

static constraints = { username blank: false, unique: true password blank: false }

static mapping = { password column: '`password`' }

Set<Role> getAuthorities() { UserRole.findAllByUser(this).collect { it.role } as Set } }

Optionally, add other properties such as email, firstName, lastName, and convenience methods, and so on:

package com.mycompany.myapp

class User {

String username String password boolean enabled String email String firstName String lastName

static constraints = { username blank: false, unique: true password blank: false }

Set<Role> getAuthorities() { UserRole.findAllByUser(this).collect { it.role } as Set }

def someMethod { … } }

The getAuthorities() method is analagous to defining static hasMany = [authorities: Authority] in a traditional many-to-many mapping. This way GormUserDetailsService can call user.authorities during login to retrieve the roles without the overhead of a bidirectional many-to-many mapping.

The class and property names are configurable using these configuration attributes:

PropertyDefault ValueMeaning
userLookup.userDomainClassName'Person'User class name
userLookup.usernamePropertyName'username'User class username field
userLookup.passwordPropertyName'password'User class password field
userLookup.authoritiesPropertyName'authorities'User class role collection field
userLookup.enabledPropertyName'enabled'User class enabled field
userLookup.accountExpiredPropertyName'accountExpired'User class account expired field
userLookup.accountLockedPropertyName'accountLocked'User class account locked field
userLookup.passwordExpiredPropertyName'passwordExpired'User class password expired field
userLookup.authorityJoinClassName'PersonAuthority'User/Role many-many join class name

4.2 Authority Class

The Spring Security plugin also requires an 'authority' class to represent a user's role(s) in the application. In general this class restricts URLs to users who have been assigned the required access rights. A user can have multiple roles to indicate various access rights in the application, and should have at least one. A basic user who can access only non-restricted resources but can still authenticate is a bit unusual. Spring Security usually functions fine if a user has no granted authorities, but fails in a few places that assume one or more. So if a user authenticates successfully but has no granted roles, the plugin grants the user a 'virtual' role, ROLE_NO_ROLES. Thus the user satisfies Spring Security's requirements but cannot access secure resources, as you would not associate any secure resources with this role.

Like the 'person' class, the 'authority' class has a default name, Authority, and a default name for its one required property, authority. If you want to use another existing domain class, it simply has to have a property for name. As with the name of the class, the names of the properties can be whatever you want - they're specified in grails-app/conf/Config.groovy.

Assuming you choose com.mycompany.myapp as your package, and Role as your class name, you'll generate this class:

package com.mycompany.myapp

class Role {

String authority

static mapping = { cache true }

static constraints = { authority blank: false, unique: true } }

The class and property names are configurable using these configuration attributes:

PropertyDefault ValueMeaning
authority.className'Authority'Role class name
authority.nameField'authority'Role class role name field

4.3 PersonAuthority Class

The typical approach to mapping the relationship between 'person' and 'authority' is a many-to-many. Users have multiple roles, and roles are shared by multiple users. This approach can be problematic in Grails, because a popular role, for example, ROLE_USER, will be granted to many users in your application. GORM uses collections to manage adding and removing related instances and maps many-to-many relationships bidirectionally. Granting a role to a user requires loading all existing users who have that role because the collection is a Set. So even though no uniqueness concerns may exist, Hibernate loads them all to enforce uniqueness. The recommended approach in the plugin is to map a domain class to the join table that manages the many-to-many, and using that to grant and revoke roles to users.

Like the other domain classes, this class is generated for you, so you don't need to deal with the details of mapping it. Assuming you choose com.mycompany.myapp as your package, and User and Role as your class names, you'll generate this class:

package com.testapp

import org.apache.commons.lang.builder.HashCodeBuilder

class UserRole implements Serializable {

User user Role role

boolean equals(other) { if (!(other instanceof UserRole)) { return false }

other.user?.id == user?.id && other.role?.id == role?.id }

int hashCode() { def builder = new HashCodeBuilder() if (user) builder.append(user.id) if (role) builder.append(role.id) builder.toHashCode() }

static UserRole get(long userId, long roleId) { find 'from UserRole where user.id=:userId and role.id=:roleId', [userId: userId, roleId: roleId] }

static UserRole create(User user, Role role, boolean flush = false) { new UserRole(user: user, role: role).save(flush: flush, insert: true) }

static boolean remove(User user, Role role, boolean flush = false) { UserRole instance = UserRole.findByUserAndRole(user, role) if (!instance) { return false }

instance.delete(flush: flush) true }

static void removeAll(User user) { executeUpdate 'DELETE FROM UserRole WHERE user=:user', [user: user] }

static mapping = { id composite: ['role', 'user'] version false } }

The helper methods make it easy to grant or revoke roles. Assuming you have already loaded a user and a role, you grant the role to the user as follows:

User user = …
Role role = …
UserRole.create user, role

Or by using the 3-parameter version to trigger a flush:

User user = …
Role role = …
UserRole.create user, role, true

Revoking a role is similar:

User user = …
Role role = …
UserRole.remove user, role

Or:

User user = …
Role role = …
UserRole.remove user, role, true

The class name is the only configurable attribute:

PropertyDefault ValueMeaning
userLookup.authorityJoinClassName'PersonAuthority'User/Role many-many join class name

4.4 Requestmap Class

Optionally, use this class to store request mapping entries in the database instead of defining them with annotations or in Config.groovy. This option makes the class configurable at runtime; you can add, remove and edit rules without restarting your application.

PropertyDefault ValueMeaning
requestMap.className'Requestmap'requestmap class name
requestMap.urlField'url'URL pattern field name
requestMap.configAttributeField'configAttribute'authority pattern field name

Assuming you choose com.mycompany.myapp as your package, and Requestmap as your class name, you'll generate this class:

package com.testapp

class Requestmap {

String url String configAttribute

static mapping = { cache true }

static constraints = { url blank: false, unique: true configAttribute blank: false } }

To use Requestmap entries to guard URLs, see Requestmap Instances Stored in the Database.