JSecurity Plugin - Quick Start Guide
This guide will quickly get you up and running with a standard setup for your web application's authentication and authorisation needs. Note that JSecurity is more flexible than described in this example, so please do visit the reference documentation later.
Installation
- First install the plugin into your project: grails install-plugin jsecurity
- Then, run grails create-db-realm.
Securing your web application
The controllers
The plugin applies access control settings to individual controllers, so that is where we will start. First, if you want to secure a particular controller, it must extend JsecAuthBase like so:
class MyController extends JsecAuthBase {
...
}
Next, you need to specify which actions require which roles and/or permissions. To do this, simply add a static property called accessControl and initialise it with a closure:
class MyController extends JsecAuthBase { static accessControl = { // All actions require the 'Observer' role. role(name: 'Observer') // The 'edit' action requires the 'Administrator' role. role(name: 'Administrator', action: 'edit') // Alternatively, several actions can be specified. role(name: 'Administrator', only: [ 'create', 'edit', 'save', 'update' ]) } ... }
The above example only shows roles, but you can also restrict actions to particular permissions. To do this, you first have to create some permissions that implement the org.jsecurity.authz.Permission interface. Fortunately, there is an abstract class that you can extend that takes care of most of the implementation requirements:
import org.jsecurity.authz.AbstractPermission class BasicPermission extends AbstractPermission { private static allowedActions = Collections.unmodifiableSet([ 'view', 'create', 'modify', 'delete' ] as Set) // The constructor for a permission should have two parameters, for the target and actions. // If only one parameter is provided, it should be the string for the target, since this expected // by JsecDbRealm.findConstructor BasicPermission(String target, String actions) { super(target, actions) } BasicPermission(String target, List actions) { super(target, actions as Set) } Set getPossibleActions() { return allowedActions } }
Note that the first constructor, the one that takes two string arguments, is required. The other one is optional, but can be more useful from your own code.
Ok, so once we have a permission, we can use it to restrict access to particular actions:
class MyController extends JsecAuthBase { static accessControl = { permission(perm: new BasicPermission('myTarget', [ 'view' ]), action: 'view') permission(perm: new BasicPermission('myTarget', [ 'modify' ]), only: [ 'edit', 'update' ]) permission(perm: new BasicPermission('myTarget', [ 'create' ]), only: [ 'create', 'save' ]) } ... }
You can also use a permission to restrict access to particular domain objects managed by a controller:
class MyController extends JsecAuthBase { def view = { BasicPermission permission = new BasicPermission(params.name, "view") ThreadLocalSecurityContext securityContext = new ThreadLocalSecurityContext() if (!securityContext.implies(permission))) { redirect(controller:'auth', action:'unauthorized') return } } }
Note that if the user isn't logged in, this will simply redirect to the unauthorized page rather than prompting the user to log in. You may also want to restrict access to the controller or action with a role-based accessControl.
The realms
The above section showed how to restrict access to particular actions, but how are the role and permission requirements enforced? Well, JsecAuthBase provides a beforeInterceptor that delegates the role and permission checks to any realms that have been defined. The final step of the installation created a standard realm that uses domain classes to store the user, role, and permission information: grails-app/realms/DbRealm.groovy. This means that your web application is already secured. Unfortunately, without the user, role, and permission information in the database, the authority checks will always fail.
Currently, there are only two ways of getting that information into the database: on startup, or via your own user interface. The first option is the simplest:
import org.apache.commons.codec.digest.DigestUtils ... class ApplicationBootStrap { def init = { servletContext -> def adminRole = JsecRole.findByName("Administrator") if(!adminRole){ adminRole = new JsecRole(name: 'Administrator') adminRole.save() } def observerRole = JsecRole.findByName('Observer') if(!observerRole){ observerRole = new JsecRole(name: 'Observer') observerRole.save() } // Create some users // Note that we store a hash of the user's password, // not the password itself. def admin = JsecUser.findByUsername('admin') if(!admin){ admin = new JsecUser(username: 'admin', passwordHash: DigestUtils.shaHex('changeit')) admin.save() new JsecUserRoleRel(user: admin, role: adminRole).save() new JsecUserRoleRel(user: admin, role: observerRole).save() } def tarzan = JsecUser.findByUsername("tarzan") if(!tarzan){ tarzan = new JsecUser(username: 'tarzan', passwordHash: DigestUtils.shaHex('password')) tarzan.save() new JsecUserRoleRel(user: tarzan, role: observerRole).save() } // Give the second user a direct permission. def p1 = new JsecPermission(type: 'BasicPermission', possibleActions: 'create,delete,modify,view') p1.save() new JsecUserPermissionRel(user: tarzan, permission: p1, target: 'myTarget', actions: 'view').save() ... } ... }
The second option can readily be done by scaffolding all the Jsec* domain classes, although you have to remember to store the SHA hashed password in the database. Of course, a UI generated this way isn't great, but it will work.
Note that you can also assign permissions to roles via the JsecRolePermissionRel table. If a user is given a role that has permissions associated with it, then that user is automatically granted all those permissions.
The login screen
This release of the plugin comes with an AuthController and a login page. Unauthenticated users are redirected to this page when they attempt to access a secure page. The controller also comes with actions for logging out, signOut, and unauthorised access, unauthorised. You must currently provide your own links to the signOut action and a corresponding view.
You should edit signOut to redirect to the start page of your choice:
def signOut = {
def threadContext = ThreadLocalSecurityContext.current()
threadContext?.invalidate
redirect(controller:"book", action:"index")
}