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 about how to apply this solution quickly, and show you some tips that should be really useful for beginners.

So let’s download the plugin.

1
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 you to 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 in your application should be such feature as authorized user access, you should run the script before constructing the domain model, just trust my experience - this will save you a lot of time.

When you will run the script, it will ask you to run it with the specification of the classes names that should be generated, so it could generate domain User class, 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 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 user in case if he will try to access urls, to which he has no access rights. The script will also add following lines to your Config.groovy file:

1
2
3
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 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 at one of my previous posts: http://sysgears.com/articles/filtering-user-access-grails So also we should talk about the request map. Basically it is the same tool as intercept URL map. The only difference is that you may change or add access configurations without the need of restarting the application, and if you are not gonna use this feature, there is no need to use request map. Anyway here is the example of usage. To use the request map tool, in the Config.groovy file add the following code:

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

com.sysgears.example.SecurityMap is a class, which instances represents URL access configuration. This class should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:

1
2
3
4
5
6
7
8
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 user logged in or not.

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

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

1
2
3
4
5
6
<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 to the page only if 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 will get your configured app to work you may run into this problem. When user will login to the application, he may receive an error: "HTTP Status 404. The requested resource (/favicon.ico) is not available." To fix this you should specify 'ISAUTHENTICATEDANONYMOUSLY' rights for the URL ‘/favicon.ico’. Here is the detailed explanation of this issue.
The feature you may need to implement in your application immediately without the detailed learning of the documentation is to use not only username for user login. For example, it would be nice to give user the ability to login using email. You may implement this feature this way: create a service that will implement GrailsUserDetailsService interface and include it to the Spring resources. Add the following code to the conf/spring/resources.groovy:

1
2
3
beans {
   userDetailsService(com.sysgears.example.CustomUserDetailsService)
}

To give user an ability to login with his email in the implemented method loadUserByUsername(String username) user should be found this way:

1
User user = User.findByUsernameOrEmail(username, username)

So code should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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 info please read the documentation.

Andrey Shevchenko,
SysGears