Spring Security Grails Plugin. Quick Start and Some Tips

Spring Security plugin is a fast and convenient solution for authorizing user access. Almost every Grails web application uses it. In this article, I would like to provide a guide on how to apply this solution quickly and show you some tips that should be really useful for beginners.

So let’s download the plugin.

grails install-plugin spring-security-core

Install output tells us “Next run the “s2-quickstart” script to initialize Spring Security and create your domain classes”. If you haven’t used this plugin before, I strongly recommend that you run the script. Classes that will be generated by the script will have some properties and methods that are necessary or strongly recommended for running the application powered by Spring Security. Actually, if you know that your application requires an authorized user access feature, you should run the script before constructing the domain model. Just trust my experience – this will save you a lot of time.

When you run the script, it will ask you to run it with the specification of the class names that should be generated, so it could generate the Domain User class, the User’s Roles class, and the User/Role many-to-many join class. Also, script optionally offers to enter request map class name, if you are beginner, let’s just move on, don’t worry about the case if you will need it later, it’s easy to configure it by yourself, we’ll get to that. Finally, let’s run the following command:
s2-quickstart com.sysgears.example User Role
Notice that the script also generated controllers Login and Logout, and the auth and denied GSP pages. Auth is a user’s login page, and Denied is a page that will be shown to the user in case they try to access URLs to which they have no access rights. The script will also add the following lines to your Config.groovy file:

grails.plugins.springsecurity.userLookup.userDomainClassName = 'com.sysgears.example.User'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'com.sysgears.example.UserRole'
grails.plugins.springsecurity.authority.className = 'com.sysgears.example.Role'

If you want to look at all the properties that could be configured here
and their detailed description, please read the appropriate documentation section. Now, let’s get to adding the access control. There are three ways to authorize access with the Spring Security tools. These ways are to use @Secured annotations, intercept URL map, and the request map. Brief description of the first two tools usage you may find in one of my previous posts.
We should also talk about the request map. Basically, it is the same tool as the intercept URL map. The only difference is that you may change or add access configurations without the need to restart the application, and if you are not gonna use this feature, there is no need to use the request map. Anyway, here is an example of usage. To use the request map tool, in the Config.groovy file, add the following code:

grails.plugins.springsecurity.requestMap.className = 'com.sysgears.example.SecurityMap'
grails.plugins.springsecurity.securityConfigType = SecurityConfigType.Requestmap

com.sysgears.example.SecurityMap is a class whose instances represent URL access configuration. This class should look like this:

package com.sysgears.example

class SecurityMap {

   String url
   String configAttribute

   static mapping = {
       cache true
   }

   static constraints = {
       url blank: false, unique: true
       configAttribute blank: false
   }
}

You will notice that configuring is pretty similar to the intercept URL map config type:

new SecurityMap(url: '/js/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save(flush:true)
new SecurityMap(url: '/css/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save(flush:true)
new SecurityMap(url: '/images/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save(flush:true)
new SecurityMap(url: '/login/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save(flush:true)
new SecurityMap(url: '/logout/**', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save(flush:true)
new SecurityMap(url: '/favicon.ico', configAttribute: 'IS_AUTHENTICATED_ANONYMOUSLY').save(flush:true)
new SecurityMap(url: '/admin/**', configAttribute: 'ROLE_ADMIN').save(flush:true)
new SecurityMap(url: '/*/**', configAttribute: 'ROLE_ADMIN, ROLE_USER').save(flush:true)

Spring Security plugin also has a bunch of helper tools that should be really helpful for your app. Controllers and services will often use the Spring Security Service. For example, one of the most common usages is to get username of current user, it can be done this way: springSecurityService.authentication.name Another common usage is to execute the code depending on whether the user is logged in or not.

   if (!springSecurityService.isLoggedIn()) {
       //do something
   } 

Another great helpers from the Spring Security plugin are taglibs. They are used when you need to restrict access to some information directly on the GSP pages. In my applications, I have a lot of this kind of usage:

<sec:ifLoggedIn>You are logged in as ${user.username}</sec:ifLoggedIn>
<sec:ifAnyGranted roles="ROLE_ADMIN">Some admin info</sec:ifAnyGranted>
<sec:ifNotLoggedIn>
  <g:link controller="login" action='index'>Login</g:link>
  <g:link controller="register" action='index'>Sign Up</g:link> 
</sec:ifNotLoggedIn>

Sure, the text inside the tags is included in the page only if the condition specified in the tag is true. There are also a lot of stuff that will help you to develop Spring Security powered application, for the full information about, please read the appropriate documentation section.

When you get your configured app to work, you may run into this problem. When the user logs in to the application, they may receive an error: “HTTP Status 404. The requested resource (/favicon.ico) is not available.” To fix this, you should specify ‘IS_AUTHENTICATED_ANONYMOUSLY’ rights for the URL ‘/favicon.ico’.

The feature you may need to implement in your application immediately, without the detailed learning of the documentation, is to use not only the username for user login. For example, it would be nice to give users the right to log in using email. You may implement this feature this way: create a service that will implement the GrailsUserDetailsService interface and include it in the Spring resources. Add the following code to the conf/spring/resources.groovy:

beans {
   userDetailsService(com.sysgears.example.CustomUserDetailsService)
}

To give the user the ability to log in with their email in the implemented method loadUserByUsername(String username), the user should be found in this way:

User user = User.findByUsernameOrEmail(username, username)

So the code should look like this:

class CustomUserDetailsService implements GrailsUserDetailsService {

   /**
    * Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least one role, so
    * we give a user with no granted roles this one which gets past that restriction but
    * doesn't grant anything.   
    */
   static final List NO_ROLES = 
           [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]

   UserDetails loadUserByUsername(String username, boolean loadRoles) 
           throws UsernameNotFoundException {
       return loadUserByUsername(username)
   }

   UserDetails loadUserByUsername(String username)
           throws UsernameNotFoundException {
   User.withTransaction { status ->
       User user = User.findByUsernameOrEmail(username, username)
           if (!user) {
               throw new UsernameNotFoundException('User not found', 
                       username)
           }
           def authorities = user.authorities.collect { 
               new GrantedAuthorityImpl(it.authority)
           }

           return new GrailsUser(user.username,
                   user.password,
                   user.enabled,
                   !user.accountExpired,
                   !user.passwordExpired,
                   !user.accountLocked,
                   authorities ?: NO_ROLES,
                   user.id)
       }
   }
}

Hope this quickstart guide was helpful for you. For more information, read the following documentation.