22 Tutorials - Reference Documentation
Authors: Burt Beckwith, Beverley Talbott
Version: 2.0.0
Table of Contents
22 Tutorials
22.1 Using Controller Annotations to Secure URLs
1. Create your Grails application.
$ grails create-app bookstore $ cd bookstore
2. Install the plugin by adding it to BuildConfig.groovy
plugins { … compile ':spring-security-core:2.0.0' }
$ grails compile
3. Create the User and Role domain classes.
$ grails s2-quickstart com.testapp User Role
Depending on your database, some domain class names might not be valid, especially those relating to security. Before you create names like "User" or "Group", make sure they are not reserved keywords in your database. or escape the name with backticks in theThe script creates this User class:mapping
block, e.g.static mapping = { table '`user`' }
package com.testappimport groovy.transform.EqualsAndHashCode import groovy.transform.ToString@EqualsAndHashCode(includes='username') @ToString(includes='username', includeNames=true, includePackage=false) class User implements Serializable { private static final long serialVersionUID = 1 transient springSecurityService String username String password boolean enabled = true boolean accountExpired boolean accountLocked boolean passwordExpired User(String username, String password) { this() this.username = username this.password = password } Set<Role> getAuthorities() { UserRole.findAllByUser(this)*.role } def beforeInsert() { encodePassword() } def beforeUpdate() { if (isDirty('password')) { encodePassword() } } protected void encodePassword() { password = springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(password) : password } static transients = ['springSecurityService'] static constraints = { username blank: false, unique: true password blank: false } static mapping = { password column: '`password`' } }
Earlier versions of the plugin didn't include password hashing logic in the domain class, but it makes the code a lot cleaner.and this Role class:
package com.testappimport groovy.transform.EqualsAndHashCode import groovy.transform.ToString@EqualsAndHashCode(includes='authority') @ToString(includes='authority', includeNames=true, includePackage=false) class Role implements Serializable { private static final long serialVersionUID = 1 String authority Role(String authority) { this() this.authority = authority } static constraints = { authority blank: false, unique: true } static mapping = { cache true } }
UserRole
:package com.testappimport grails.gorm.DetachedCriteria import groovy.transform.ToStringimport org.apache.commons.lang.builder.HashCodeBuilder@ToString(cache=true, includeNames=true, includePackage=false) class UserRole implements Serializable { private static final long serialVersionUID = 1 User user Role role UserRole(User u, Role r) { this() user = u role = r } @Override boolean equals(other) { if (!(other instanceof UserRole)) { return false } other.user?.id == user?.id && other.role?.id == role?.id } @Override 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) { criteriaFor(userId, roleId).get() } static boolean exists(long userId, long roleId) { criteriaFor(userId, roleId).count() } private static DetachedCriteria criteriaFor(long userId, long roleId) { UserRole.where { user == User.load(userId) && role == Role.load(roleId) } } static UserRole create(User user, Role role, boolean flush = false) { def instance = new UserRole(user: user, role: role) instance.save(flush: flush, insert: true) instance } static boolean remove(User u, Role r, boolean flush = false) { if (u == null || r == null) return false int rowCount = UserRole.where { user == u && role == r }.deleteAll() if (flush) { UserRole.withSession { it.flush() } } rowCount } static void removeAll(User u, boolean flush = false) { if (u == null) return UserRole.where { user == u }.deleteAll() if (flush) { UserRole.withSession { it.flush() } } } static void removeAll(Role r, boolean flush = false) { if (r == null) return UserRole.where { role == r }.deleteAll() if (flush) { UserRole.withSession { it.flush() } } } static constraints = { role validator: { Role r, UserRole ur -> if (ur.user == null || ur.user.id == null) return boolean existing = false UserRole.withNewSession { existing = UserRole.exists(ur.user.id, r.id) } if (existing) { return 'userRole.exists' } } } static mapping = { id composite: ['user', 'role'] version false } }
grails-app/conf/Config.groovy
and added the configuration for your domain classes. Make sure that the changes are correct.
These generated files are not part of the plugin - these are your application files. They are examples to get you started, so you can edit them as you please. They contain the minimum needed for the plugin's default implementation of the Spring Security UserDetailsService
(which like everything in the plugin is customizable).
The plugin has no support for CRUD actions or GSPs for your domain classes; the spring-security-ui
plugin supplies a UI for those. So for now you will create roles and users in grails-app/conf/BootStrap.groovy
. (See step 7.)4. Create a controller that will be restricted by role.
$ grails create-controller com.testapp.Secure
grails-app/controllers/com/testapp/ SecureController.groovy
. Add some output so you can verify that things are working:package com.testappclass SecureController {
def index() {
render 'Secure access only'
}
}
5. Edit grails-app/conf/BootStrap.groovy to add a test user.
import com.testapp.Role import com.testapp.User import com.testapp.UserRoleclass BootStrap { def init = { servletContext -> def adminRole = new Role('ROLE_ADMIN').save() def userRole = new Role('ROLE_USER').save() def testUser = new User('me', 'password').save() UserRole.create testUser, adminRole, true assert User.count() == 1 assert Role.count() == 2 assert UserRole.count() == 1 } }
BootStrap.groovy
:
- The example does not use a traditional GORM many-to-many mapping for the User<->Role relationship; instead you are mapping the join table with the
UserRole
class. This performance optimization helps significantly when many users have one or more common roles. - We explicitly flush (using the 3-arg
UserRole.create()
call) becauseBootStrap
does not run in a transaction or OpenSessionInView.
6. Start the server.
$ grails run-app
7. Before you secure the page, navigate to http://localhost:8080/bookstore/secure to verify that you cannot access see the page yet. You will be redirected to the login page, but after a successful authentication (log in with the username and password you used for the test user in BootStrap.groovy) you will see an error page:
Sorry, you're not authorized to view this page.
8. Edit grails-app/controllers/SecureController.groovy to import the annotation class and apply the annotation to restrict (and grant) access.
package com.testappimport grails.plugin.springsecurity.annotation.Securedclass SecureController { @Secured('ROLE_ADMIN') def index() { render 'Secure access only' } }
@Secured('ROLE_ADMIN') class SecureController { def index() { render 'Secure access only' } }
9. Shut down the app and run grails run-app
again, and navigate again to http://localhost:8080/bookstore/secure.
This time you should again be able to see the secure page after successfully authenticating.10. Test the Remember Me functionality.
Check the checkbox, and once you've tested the secure page, close your browser and reopen it. Navigate again the the secure page. Because a is cookie stored, you should not need to log in again. Logout at any time by navigating to http://localhost:8080/bookstore/logout.11. Optionally, create a CRUD UI to work with users and roles.
Run grails generate-all for the domain classes:
$ grails generate-all com.testapp.User
$ grails generate-all com.testapp.Role