Grails: How To Secure Your Application Using Spring Security Core

A step by step tutorial on how to use Spring Security Core to secure your Grails application.

Introduction

Any web-based application must have a mechanism for authenticating users and authorising them to do their defined activities in the system. One can go for the traditional approach of doing it with a login form; it works for the start. But on today's Internet, a classic pair of username and password is not always available as many people prefer to use a single OpenID, Twitter or Facebook account to access their data across different web sites. Also in corporate environments usually authentication and authorisation is done against an LDAP database.

This is where Spring Security collection comes to play by allowing you to connect to a wide range of data sources and acquire access information from them instead of strong them yourself.

At the heart of Spring Security lies Spring Security Core and as usual there's a Grails plugin for that! Let's see how we can use it.

NOTE: This tutorial has been updated to work with Grails 2.3.0 and SpringSecurityCore 1.2.7.3. All thanks to readers' feedbacks.

Create the project

Before starting off, obviously, we have to create a new experimental project. We'll call it "secureapp". Run grails create-app secureapp to create it.

Configure Spring Security Core

First of all, we have to install Spring Security Core plugin into our project. Edit secureapp/grails-app/conf/BuildConfig.groovy and modify the plugins section as below:
plugins {
        // plugins for the build system only
        build ":tomcat:7.0.42"

        // plugins for the compile step
        compile ":scaffolding:2.0.0"
        compile ':cache:1.1.1'

        // plugins needed at runtime but not for compilation
        runtime ":hibernate:3.6.10.1" // or ":hibernate4:4.1.11.1"
        runtime ":database-migration:1.3.5"
        runtime ":jquery:1.10.2" // <-- If using 1.8.3, update to this version
        runtime ":resources:1.2"

        compile ":spring-security-core:1.2.7.3"  // <-- Added
    }
Then run (optionally grails clean) grails compile while in project's directory to have the plugin installed.
The next step is to have Spring Security Core create the required models and controllers for us. Drop into Grails shell (just type grails in project's directory) and run s2-quickstart to get it done.
C:\Users\Bahman\Source\secureapp>grails
| Enter a script name to run. Use TAB for completion:

grails> s2-quickstart com.bahmanm.secureapp SecAppUser SecAppRole
*******************************************************
* Created domain classes, controllers, and GSPs. Your *
* grails-app/conf/Config.groovy has been updated with *
* the class names of the configured domain classes;   *
* please verify that the values are correct.          *
*******************************************************
The script created 3 domain classes in domain/com/bahmanm/secureapp/: SecAppUser and SecAppRole which obvioulsy stand for user and role entities respectively, and SecAppUserSecAppRole which is the many-to-many relationship between them --It's been implemented like this instead of GORM's standard many-to-many feature for performance reasons. Also in controllers/ it created LoginController and LogoutController which along with views/login/auth.gsp and views/login/denied.gsp form our project's login/logout pages.
Now before moving any further, we should first take care of some funny behaviour (bug?). Open conf/Config.groovy. At the end of file you see three lines which Spring Security configurations:
// Added by the Spring Security Core plugin:
grails.plugins.springsecurity.userLookup.userDomainClassName = 'com.bahmanm.secureapp.SecAppUser'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'com.bahmanm.secureapp.SecAppUserSecAppRole'
grails.plugins.springsecurity.authority.className = 'com.bahmanm.secureapp.SecAppRole'
Now for some reason -unknown to me- if you leave those lines at the end, Grails will keep popping up "500 Internal Server Error - The specified user domain class 'Person' is not a domain class" into your face! The solution is simple: move those lines above Log4j configuration lines so that the configuration file looks like below (pay attention to lines 12-15):
environments {
    development {
        grails.logging.jul.usebridge = true
    }
    production {
        grails.logging.jul.usebridge = false
        // TODO: grails.serverURL = "http://www.changeme.com"
    }
}

// Added by the Spring Security Core plugin:
grails.plugins.springsecurity.userLookup.userDomainClassName = 'com.bahmanm.secureapp.SecAppUser'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'com.bahmanm.secureapp.SecAppUserSecAppRole'
grails.plugins.springsecurity.authority.className = 'com.bahmanm.secureapp.SecAppRole'


// log4j configuration
log4j = {
    // Example of changing the log pattern for the default console appender:
    //
    //appenders {
    //    console name:'stdout', layout:pattern(conversionPattern: '%c{2} %m%n')
    //}

    error  'org.codehaus.groovy.grails.web.servlet',        // controllers
           'org.codehaus.groovy.grails.web.pages',          // GSP
           'org.codehaus.groovy.grails.web.sitemesh',       // layouts
           'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
           'org.codehaus.groovy.grails.web.mapping',        // URL mapping
           'org.codehaus.groovy.grails.commons',            // core / classloading
           'org.codehaus.groovy.grails.plugins',            // plugins
           'org.codehaus.groovy.grails.orm.hibernate',      // hibernate integration
           'org.springframework',
           'org.hibernate',
           'net.sf.ehcache.hibernate'
}
At this stage, Spring Security Core is configured properly, just one minor point: since we're using in-memory database right now we have to create the users/roles each time we run the application (this is not an issue if you use a persistent database like PostgreSQL). Edit conf/BootStrap.groovy to tell Grails about our sample users/roles.
import com.bahmanm.secureapp.SecAppRole
import com.bahmanm.secureapp.SecAppUser
import com.bahmanm.secureapp.SecAppUserSecAppRole

class BootStrap {

  def init = { servletContext ->
    def adminRole = new SecAppRole(authority: 'ROLE_ADMIN').save(flush: true)
    def userRole = new SecAppRole(authority: 'ROLE_USER').save(flush: true)

    def testUser = new SecAppUser(username: 'admin', enabled: true, password: 'admin')
    testUser.save(flush: true)

    SecAppUserSecAppRole.create testUser, adminRole, true

    assert SecAppUser.count() == 1
    assert SecAppRole.count() == 2
    assert SecAppUserSecAppRole.count() == 1
  }
  
  def destroy = {
  }
}

Something to Secure

Now let's create a controller and secure it using the foundations we just laid: grails create-controller com.bahmanm.secureapp.SensitiveContentController. Edit the file and make it render something very trivial for now:
package com.bahmanm.secureapp

import org.springframework.security.access.annotation.Secured
/*
 If you're using older Grails version like 2.2.x series use the
 following instead:
   import grails.plugin.springsecurity.annotation.Secured
 */

class SensitiveContentController {
  
  @Secured(['ROLE_ADMIN'])
  def index() {
    render "Some sensitive content"
  }
}
Now if you run the application (grails run-app) and browse to http://localhost:8080/secureapp/sensitiveContent/index you will be redirected to login page (username and password both are "admin").

Conclusion

Following a few simple steps mentioned in this article, you can secure your Grails application with battle-hardened and proven Spring Security Core.

Notes

Spring Security Core plugin has a very comprehensive set of documentation which proved to be very handy to me.

Image source: elderscrolls.wikia.com

Comments

Popular posts from this blog

Variables in GNU Make: Simple and Recursive

Checkmate on Your Terms: A Personal Journey with Correspondence Chess

Firefox profiles: Quickly replicate your settings to any machine