23 Tutorials - Reference Documentation
Authors: Burt Beckwith, Beverley Talbott
Version: 1.2.7.3
Table of Contents
23 Tutorials
23.1 Using Controller Annotations to Secure URLs
1. Create your Grails application.
$ grails create-app bookstore $ cd bookstore
2. Install the plugin.
$ grails install-plugin spring-security-core
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.The script creates this User class:
package com.testapppackage testclass User { transient springSecurityService 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 } def beforeInsert() { encodePassword() } def beforeUpdate() { if (isDirty('password')) { encodePassword() } } protected void encodePassword() { password = springSecurityService.encodePassword(password) } }
Earlier versions of the plugin didn't include password encryption logic in the domain class, but it makes the code a lot cleaner.and this Role class:
package com.testappclass Role { String authority static mapping = { cache true } static constraints = { authority blank: false, unique: true } }
UserRole:package com.testappimport org.apache.commons.lang.builder.HashCodeBuilderclass 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 } }
grails-app/controllers/LoginController.groovygrails-app/controllers/LogoutController.groovygrails-app/views/auth.gspgrails-app/views/denied.gsp
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.The plugin has no support for CRUD actions and GSPs for your domain classes; the
spring-security-ui plugin will supply 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. Start the server.
$ grails run-app
6. Before you secure the page, navigate to http://localhost:8080/bookstore/secure to verify that you can see the page without being logged in.
7. Shut down the app (using CTRL-C) and edit grails-app/conf/BootStrap.groovy to add the security objects that you need.
import com.testapp.Role import com.testapp.User import com.testapp.UserRoleclass BootStrap { def init = { servletContext -> def adminRole = new Role(authority: 'ROLE_ADMIN').save(flush: true) def userRole = new Role(authority: 'ROLE_USER').save(flush: true) def testUser = new User(username: 'me', enabled: true, password: 'password') testUser.save(flush: true) 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
UserRoleclass. This performance optimization helps significantly when many users have one or more common roles. - We explicitly flushed the creates because
BootStrapdoes not run in a transaction or OpenSessionInView.
8. Edit grails-app/controllers/SecureController.groovy to import the annotation class and apply the annotation to restrict access.
package com.testappimport grails.plugins.springsecurity.Securedclass SecureController { @Secured(['ROLE_ADMIN']) def index = { render 'Secure access only' } }
package com.testappimport grails.plugins.springsecurity.Secured@Secured(['ROLE_ADMIN']) class SecureController { def index = { render 'Secure access only' } }
9. Run grails run-app again and navigate to http://localhost:8080/bookstore/secure.
This time, you should be presented with the login page. Log in with the username and password you used for the test user, and you should again be able to see the secure page.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
23.2 Migration From the Acegi Plugin
In this tutorial we'll discuss the general steps required to migrate from the Acegi plugin to the Spring Security Core plugin. A lot of the material here comes from an email that Lubos Pochman sent to the User mailing list documenting the steps he took to upgrade from the Acegi plugin.This isn't a standard step-by-step tutorial since every application is different and the steps required will vary from project to project. Instead these are guidelines and things to keep in mind. You should also read Section 2 and Section 3.The first thing to do is uninstall the Acegi plugin$ grails uninstall-plugin acegi
$ grails install-plugin spring-security-core
grails-app/domain/com/testapp/User.groovygrails-app/domain/com/testapp/Role.groovygrails-app/domain/com/testapp/UserRole.groovygrails-app/controllers/LoginController.groovygrails-app/controllers/LogoutController.groovygrails-app/views/login/auth.gspgrails-app/views/login/denied.gsp
LoginController.groovy, LogoutController.groovy, auth.gsp and denied.gsp, and overwrite your files with those. Do the same for User.groovy and Role.groovy, and move UserRole.groovy into your project.User and Role UI
You can use the standard Grailsgenerate-all script to create a UI to manage Users and Roles as described in the previous tutorial, or for a more complete solution use the Spring Security UI plugin.authenticateService
The utility service in Spring Security Core isSpringSecurityService, so you need to replace def authenticateService with def springSecurityService. Many of the methods have the same names and signatures but there are some differences:
principal()was renamed togetPrincipal()ifAllGranted(),ifNotGranted(), andifAnyGranted()were removed; useorg.codehaus.groovy.grails.plugins.springsecurity. SpringSecurityUtils.ifAllGranted(),ifNotGranted(), andifAnyGranted()insteadgetSecurityConfig()was removed, useSpringSecurityUtils.getSecurityConfig()instead
UserDetails implementation (GrailsUser) no longer has a reference to the domain class instance. This was intended to make it easy to access User class data that's not available in the Principal but it has frustrating side effects due to being a disconnected Hibernate object. Instead GrailsUser stores the user's id so you can conveniently retrieve the instance when needed. So instead ofdef user = authenticateService.userDomain() user = User.get(user.id)
def user = User.get(springSecurityService.principal.id)
Role granting
The Acegi plugin uses a standard Grails many-to-many relationship (i.e. usinghasMany and belongsTo) between User and Role but this will have performance problems if you have many users. Spring Security Core also uses a many-to-many relationship but maps the join table as a domain class instead of using collections. In the Acegi plugin you would grant a role to a user using
Role role = … User user = … role.addToPeople(user)
Role role = … User user = … role.removeFromPeople(user)
UserRole
Role role = … User user = … UserRole.create user, role
Role role = … User user = … UserRole.remove user, role
SecurityConfig.groovy
Configuration settings are now stored ingrails-app/conf/Config.groovy along with the rest of the application configuration. The primary motiviation for this change is to easily support environment-specific security settings. Migrate settings from SecurityConfig.groovy to Config.groovy (see this summary for the new names.In particular it's important that the following properties be configured (replace class and package names to match your domain classes):
grails. plugins. springsecurity. userLookup. userDomainClassName =
'com.yourcompany.yourapp.User'
grails. plugins. springsecurity. userLookup. authorityJoinClassName =
'com.yourcompany.yourapp.UserRole'
grails. plugins. springsecurity. authority. className =
'com.yourcompany.yourapp.Role'SecurityConfig.groovy when you're finished.Controller annotations
TheSecured annotation changed from org.codehaus.groovy.grails.plugins. springsecurity.Secured to grails.plugins.springsecurity.Secured. Consider using SpEL expressions since they're a lot more powerful and expressive than simple role names.Security tags
- tag names now start with 'if' instead of 'is', and the
roleattribute changed toroles, so for example change<g:ifAnyGranted role='...'>to<sec:ifAnyGranted roles='...'> - use
<sec:username/>instead of<g:loggedInUserInfo(field:'username')}/>- use<sec:loggedInUserInfo>to render otherGrailsUserattributes