Production-Grade Acegi Security for Grails

Note: See an update/upgrade to this post (moving to SpringSecurity) here.

The standard Acegi plugin for Grails provides a basic implementation of the Acegi security framework for the Grails web stack.  It does a nice job of setting up a basic filter chain that can be rather cumbersome to establish from scratch.  It comes hard-wired with a simple DAO-based authentication provider, and includes the pieces needed to create a rudimentary security scheme for your Grails application.

But the standard plugin is not very flexible, and does not provide alternate means of authentication (such as LDAP).  It is also cumbersome to configure, and it is difficult to manage authorization mappings.  I also didn’t like the “optimistic” authorization scheme (anything not locked down is publicly available).  This article demonstrates a security plugin implementation for Grails that meets a higher level of security requirements.

Basic Acegi Security Concepts

Let’s first define some security terminology for the purposes of this discussion.  Specifically, there are two components of web security that will be discussed here — authentication and authorization:

  • Authentication is the process of verifying a user’s identity, typically done using a simple username/password combination.  When a user is successfully authenticated, Acegi places an authentication token on the HTTP session.  This token contains some information about the authenticated user, including the list of “granted authorities” or “roles” that the user is endowed with.  The authentication token remains active for the life of the HTTP session, or until the user explicitly logs out.
  • An authorization scheme determines which URLs or resources an authenticated user has access to.  For each HTTP request, Acegi compares the list of roles on the authentication token to the roles that are granted access to the target of the request.  If a match is not found, the user is denied access to the resource.

This sample plugin is set up with an AnonymousProcessingFilter, which means that an anonymous authentication token is placed on the session with the initial HTTP request.  This token is associated with only one, anonymous role.  This means the authorization scheme works for general access, by assigning that anonymous role to those resources that should be publicly available.

Higher-level Requirements

The standard Acegi plugin for Grails does not provide enough flexibility, configurability, or maintainability for a non-trivial production environment.  These are the most important pieces I felt were missing:

  • Flexible configuration.  It should be easy to set up, and to get a quick overview of the configuration settings.  Also, environment-specific configuration is a must.
  • Multiple authentication providers.  Provide LDAP authentication in addition to the standard DAO implementation.
  • Pessimistic authorization scheme.  Default to no access for anybody to anything, open up holes as needed.  This may not work for everybody, but my goal is to reduce the risk of leaving unintended content out there (given how easy it is to implement a Grails controller method).
  • Ease of implementation.  A developer should readily be able to configure a controller method to a certain security level at the time of feature implementation.  While I believe security should be an implementation-time decision, it should NOT be an obstacle to rapid and agile development.
  • Good maintainability.  When Grails controllers are re-factored or removed, it should be obvious what changes need to be made from a security perspective.   Changes in controller structure should not result in hours of tracing down security rules.

A Solution

I wanted a security plugin that would perform those two basic tasks of authentication and authorization, while conforming to the five major requirements outlined above.  The simplest way to explain the implementation is to walk through the various pieces and their usage step by step in a sample Grails application.

Install the Demo

Start by expanding the demo zip file in a directory of your choosing.  All further project-related paths in this article will assume that you are currently in that top-level install directory.

Notice how there are two grails projects: demo and demo-acegi.  The former is the sample application, while the latter is the acegi plugin.

You will need Grails installed to be able to run the demo, but that’s not needed for the purposes of this discussion.

Configuration

The configuration of the acegi plugin is governed by the application that uses it.  So we’ll find the acegi settings for our demo application in the application’s standard configuration file (acegi-plugin-demo/demo/grails-app/conf/Config.groovy).

There are three sets of configuration variables:  The first three variables set up some of the URLs that Acegi needs under various conditions.  The second set of variables sets up the DAO authentication providers, while the last set configures the LDAP authentication providers:

acegi.loginFormUrl='/access/notAuthorized'
acegi.logoutUrl='/access/logout'
acegi.sessionExpiredUrl='/access/sessionExpired'
// These are the DAO user details services
acegi.daoServices=[
    'DemoDAO': [
        daoService: 'userLookupService',
        processesUrl: '/j_acegi_security_check',
        alwaysUseDefaultTargetUrl: false,
        authenticationSuccessUrl: '/loginCredentials/list',
        authenticationFailureUrl: '/access/authenticationFailure'
    ]
]
// These are the LDAP authentication services
acegi.ldapServices=[
    'DemoLDAP': [
        url: 'ldap://your.ldap.url/dc=mycorp,dc=local',
        managerDn: 'CN=Some Title,CN=SomeGroup,DC=mycorp,DC=local',
        managerPassword: 'mypassword',
        userSearchBase: '',
        groupsPath: 'OU=Groups,OU=Production',
        processesUrl: '/j_acegi_security_check_ldap',
        alwaysUseDefaultTargetUrl: false,
        authenticationSuccessUrl: '/loginCredentials/list',
        authenticationFailureUrl: '/access/authenticationFailure'
    ]
]

As the configurations for the plugin are specified in the standard Config.groovy file, you can obviously make the settings environment-dependent in the standard fashion by placing them in the appropriate environment section of that file.

Notice that most of the configuration settings are URLs, and most of those point to various methods on the AccessController — this is a controller in the application, not the plugin. That means the application has full control of the various URLs pointed to by Acegi.

DAO Authentication

Under Acegi, DAO authentication is based on the DaoAuthenticationProvider class.  In essence, this provider users a ‘service’ to look up a ‘user’ to be authenticated.  The service implements the UserDetailsService interface, which brings back an implementation of UserDetails to represent the user.  There are comments in the Acegi API documentation to explain these classes/interfaces in detail.

The DAO authentication provider used in our plugin is more or less the same as used by the standard Acegi plugin for Grails, except our plugin makes it more configurable.  The actual domain classes and the UserDetailsService are provided by the application, not the plugin.  Since Acegi deals with interfaces and doesn’t need to know anything about the back-end implementation of these items, this provides maximum flexibility.

In the configuration settings for the demo DAO authentication provider, notice the daoService variable.  Its value is set to userLookupService, which is a Spring bean reference to a Grails service declared by the demo application.

That service looks like this:

import org.acegisecurity.userdetails.UserDetails
import org.acegisecurity.userdetails.UsernameNotFoundException
import org.springframework.dao.DataAccessException
import org.acegisecurity.userdetails.UserDetailsService
 
class UserLookupService implements UserDetailsService {
 
    def UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
        User user = User.findByUsername(userName)
        if (user) {
            log.debug("User found for ${userName}: " + user)
            return user
        }
        log.info("User NOT found: ${userName}")
        throw new UsernameNotFoundException("User not found.");
    }
 
}

This is about as basic an implementation of the Acegi UserDetailsService as it gets.   It simply looks up a UserDetails object based on the username passed in.  The UserDetails object is used by Acegi to create the authentication token mentioned above.

In the demo application, the UserDetails interface is implemented by the Grails domain class User, which has a one-to-many relationship to Role.  Thus, these two Grails domain classes, User and Role, form the basis for the Acegi DAO authentication token in our demo application.  These domain classes are found in the conventional Grails directory, acegi-plugin-demo/demo/grails-app/domain.

In summary, the implementation of the DAO service is completely determined by the application, not the plugin.  As long as a valid UserDetails object is returned, the actual implementation of the daoService bean is irrelevant.

LDAP Authentication

Our plugin’s implementation of an LDAP authentication service is merely offering a convenient way to configure a standard Acegi LdapAuthenticationProvider.  There are no additional pieces required by the application beyond the configuration settings as provided in acegi-plugin-demo/demo/conf/Config.groovy.

One thing to keep in mind is that the group names configured in the LDAP system must match the role names used in your application. Acegi inserts the text ‘ROLE_’ to at the beginning of each group name retrieved from LDAP and forces the rest of the group name text to all upper-case. For instance, if you have a group in your LDAP system named ‘SuperUser’, the role in your authorization mappings should read ‘ROLE_SUPERUSER’.

Authorization

In order to reach the goals of easy development and maintainability, it is essential to standardize the definition of url-to-authorization mappings.  In other words, use a convention to determine which roles are permitted to access a URL.

In the standard acegi plugin, these authorization mappings are established by manually entering rules in a database table.  That table is read by the plugin at start-up, and all authorization decisions are based on those rules.  The authorization mappings must be configured, in no direct relation to the resources they govern.

I found this to be cumbersome and error-prone – a developer working on a controller method had to enter database rows to govern the security access to the code she was working on.  To determine the access level of a particular controller’s methods, you had to do a rather complex database query.  When the rules got more fine-grained, it became very hard to determine which rule actually applied to which resource.

It would be better to have the authorization mappings defined in the controllers themselves.  The access-level of a URL would then be directly determined right next to the URL end-point that it governs, in a conventional location.

As an example, let’s examine the controller acegi-plugin-demo/demo/grails-app/controller/UserController.groovy in the demo application:

class UserController {
 
    static authorizations = [
        index: [Role.ADMIN_USER],
        list: [Role.ADMIN_USER],
        show: [Role.ADMIN_USER],
        create: [Role.ADMIN_USER],
        save: [Role.ADMIN_USER],
        edit: [Role.ADMIN_USER],
        update: [Role.ADMIN_USER]
    ]
. . .

The controller declares a simple map with its methods as keys and a list of roles as values.  In this case, only users with the ADMIN_USER role are granted access to these methods.

There are some URLs in a Grails application that do not correspond to a controller method.   These special-case mappings must be mapped somewhere else. It seemed the most logical place for that was in the UrlMappings class, as this class contains other mappings that direct traffic around the application.

As an example, let’s examine the demo application’s acegi-plugin-demo/demo/conf/UrlMappings.groovy:

class UrlMappings {
 
    static authorizations = [
        '/': Role.ALL_ROLES,
        '/js/**': Role.ALL_ROLES,
        '/css/**': Role.ALL_ROLES,
        '/images/**': Role.ALL_ROLES,
        '/logout': Role.ALL_ROLES
    ]
. . .

These mappings open up access for URLs in a path-based manner, including wild-cards (as supported by Spring’s AntPathMatcher).  The access paths are the keys and lists of roles are the values.

The authorization mappings declared in UrlMappings are fairly static, and should not be modified by developers.  While you could easily put sweeping rules around controllers in UrlMappings, that would defeat the purpose of our convention-based scheme that has the mappings in the controllers themselves.

In summary, authorization mappings are determined in two conventional places:

  • Controllers. As almost every URI into the Grails application is through a controller method, this is the best place to maintain the authorization mappings.
  • UrlMappings. Provides a way to establish authorization rules for urls or paths that are not tied to a controller or its methods, e.g. /css/** or /js/**.

Running the Demo

To start up the application, go to the demo application directory and start grails:

cd acegi-plugin-demo/demo
grails run-app

As the demo application uses a simple HQL database, it creates some bootstrap data in acegi-plugin-demo/demo/grails-app/conf/BootStrap.groovy to create a role and an initial admin user:

// Create a role for the demo application
new Role(authority: 'ROLE_ADMIN_USER', description: 'Administrative User').save()
 
// Create an admin user, and add the admin user role
def user = new User(username: 'adminUser', password: 'password'.encodeAsPassword())
user.save()
user.addToRoles(Role.findByAuthority('ROLE_ADMIN_USER'))
user.save()

Now point your browser to the home page of the application: http://localhost:8080/demo.

You will see two controllers listed — UserController and AccessController. Try clicking UserController. Notice how you are re-directed to the login page. This is Acegi re-directing you because you haven’t authenticated yet. Login using adminUser/password (as set up in the bootstrap data above), and you will be taken to the default action for the UserController. This demonstrates how the simple authorizations mappings we set up in UserController above determine how to allow access to controller methods.

Next Steps

This article covers the main goals and uses of an alternative Acegi plugin for Grails. It does not go into the details of the plugin implementation, nor does it do a terribly good job of explaining the meaning of the configurable URLs that are used by the Acegi decision process. It is my hope that the code/examples are self-documenting, but I can understand if things are unclear — especially if you are not already familiar with the workings of the Acegi framework.

This plugin can also be used with a finer-grained authorization scheme that associates permissions to roles. This allows an application to have fairly coarse-grained roles that rarely change, while finer-grained permissions maintained by the application are directly linked to access privileges on a very low level (like which buttons are enabled on a particular screen, etc.). But this is a topic for another post/article, so as not to confuse you further at this point.

Please post your comments/suggestions, and I will follow up with more posts as needed. I am planning a post on the inner workings of the plugin, but right now I’m spent…