alexgorbatchev

Monday, May 11, 2015

Grails 3 App with Security (Part 2) - Gorm-based authentication

This is the second post in the Grails 3 App with Security series, so if you have questions about dependencies, please consult the first post. In this post we will setting up Spring Security in Grails 3 using Gorm-based authentication and after finishing, I'll think be happily surprised at just how easy it is to add Gorm. Unfortunately, my laptop crashed while creating this post, so if you come across any issues with the code samples or have found a better method for anything I have presented, please provide a comment at the bottom. The github repository https://github.com/dspies/grails-3-with-security with this code will be updated shortly.

Before we begin, let's get some clean up out of the way. Grails 3 (or more specifically Spring Boot) will scream at you about having second level cache enabled, but not configured after you add the Gorm classes, so let's just disable second level cache for now. I added the following just before the datasource properties, but you are welcome to add it anywhere in application.yml or configure second level cache as needed.

grails-app/conf/application.yml

...

hibernate:
    cache:
        use_second_level_cache: false
        use_query_cache: false

---
datasource:
...

With that out of the way, let's begin by adding our Gorm domain classes (User, Authority, and UserAuthority). If you have been using Spring-Security-Core plugin for any length of time, the following classes should look pretty familiar because I basically copied from the Grails 2 plugin.

grails-app/domain/example/User.groovy

package example

class User {

    String username
    String password
    boolean enabled = true
    boolean accountExpired
    boolean accountLocked
    boolean credentialsExpired

    static constraints = {
        username blank: false, unique: true
        password blank: false
    }

    static mapping = {
        password column: '`password`'
    }

    Set getAuthorities() {
        UserAuthority.findAllByUser(this).collect { it.authority }
    }

} // Blogger really wants to close the authority 'tag', so here go Blogger 

grails-app/domain/example/Authority.groovy

package example

class Authority {

    String authority

    static mapping = {
        cache true
    }

    static constraints = {
        authority blank: false, unique: true
    }
}

grails-app/domain/example/UserAuthority.groovy

package example

import org.apache.commons.lang.builder.HashCodeBuilder

class UserAuthority implements Serializable {

    private static final long serialVersionUID = 1

    User user
    Authority authority

    boolean equals(other) {
        if (!(other instanceof UserAuthority)) {
            return false
        }

        other.user?.id == user?.id &&
            other.authority?.id == authority?.id
    }

    int hashCode() {
        def builder = new HashCodeBuilder()
        if (user) builder.append(user.id)
        if (authority) builder.append(authority.id)
        builder.toHashCode()
    }

    static UserAuthority get(long userId, long authorityId) {
        UserAuthority.where {
            user == User.load(userId) &&
            authority == Authority.load(authorityId)
        }.get()
    }

    static boolean exists(long userId, long authorityId) {
        UserAuthority.where {
            user == User.load(userId) &&
            authority == Authority.load(authorityId)
        }.count() > 0
    }

    static UserAuthority create(User user, Authority authority, boolean flush = false) {
        def instance = new UserAuthority(user: user, authority: authority)
        instance.save(flush: flush, insert: true)
        instance
    }

    static boolean remove(User u, Authority r) {
        if (u == null || r == null) return false

        int rowCount = UserAuthority.where {
            user == User.load(u.id) &&
                    authority == Authority.load(r.id)
        }.deleteAll()

        rowCount > 0
    }

    static void removeAll(User u) {
        if (u == null) return

        UserAuthority.where {
            user == User.load(u.id)
        }.deleteAll()
    }

    static void removeAll(Authority r) {
        if (r == null) return

        UserAuthority.where {
            authority == Authority.load(r.id)
        }.deleteAll()
    }

    static constraints = {
        authority validator: { Authority r, UserAuthority ur ->
            if (ur.user == null) return
            boolean existing = false
            UserAuthority.withNewSession {
                existing = UserAuthority.exists(ur.user.id, r.id)
            }
            if (existing) {
                return 'userAuthority.exists'
            }
        }
    }

    static mapping = {
        id composite: ['authority', 'user']
        version false
    }
}

With the domain classes for our principal/user and authorities/roles in place, let's load two users, user and admin. You could add these in the SecurityConfiguration class, but since most people reading this will be more familiar with Bootstrap, we'll go that route.

Bootstrap.groovy

import example.*
import grails.util.Environment

class BootStrap {

    def init = { servletContext ->

        switch (Environment.current) {
            case Environment.DEVELOPMENT:

                def user = new User(username: 'user', password: 'user', enabled: true, accountExpired: false, accountLocked: false, credentialsExpired: false ).save(failOnError: true)
                def admin = new User(username: 'admin', password: 'admin', enabled: true, accountExpired: false, accountLocked: false, credentialsExpired: false ).save(failOnError: true)

                def roleUser = new Authority(authority: 'ROLE_USER').save(failOnError: true)
                def roleAdmin = new Authority(authority: 'ROLE_ADMIN').save(failOnError: true)

                UserAuthority.create(user, roleUser, true)
                UserAuthority.create(admin, roleUser, true)
                UserAuthority.create(admin, roleAdmin, true)

                break
            case Environment.PRODUCTION:

                break
        }
    }
    def destroy = {
    }
}

At this point, we have not accomplished much in terms of authentication, so let's change that now. In order to use the domain classes above, we need to:

  • Create a custom UserDetails class,
  • Create a custom UserDetailsService,
  • Add a bean for userDetailsService in resources.groovy, and
  • Configure authentication with the bean in the SecurityConfiguration class

First, let's create a GrailsUser that will do little more than extend Spring Security User class. The one thing it adds is the `id` field that will hold the Gorm identifier for the User class. Again, we will raid the Spring-Security-Core plugin from Grails 2:

src/main/java/example/GrailsUser.java

package example;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

public class GrailsUser extends User {

    private static final long serialVersionUID = 1;

    private final Object id;

    /**
     * Constructor.
     *
     * @param username the username presented to the
     *        DaoAuthenticationProvider
     * @param password the password that should be presented to the
     *        DaoAuthenticationProvider
     * @param enabled set to true if the user is enabled
     * @param accountNonExpired set to true if the account has not expired
     * @param credentialsNonExpired set to true if the credentials have not expired
     * @param accountNonLocked set to true if the account is not locked
     * @param authorities the authorities that should be granted to the caller if they
     *        presented the correct username and password and the user is enabled. Not null.
     * @param id the id of the domain class instance used to populate this
     */
    public GrailsUser(String username,
                      String password,
                      boolean enabled,
                      boolean accountNonExpired,
                      boolean credentialsNonExpired,
                      boolean accountNonLocked,
                      Collection authorities,
                      Object id) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired,
                accountNonLocked, authorities);
        this.id = id;
    }

    /**
     * Get the id.
     * @return the id
     */
    public Object getId() {
        return id;
    }
}

Next, we will create our custom UserDetailsService. Like the domain classes and UserDetails class, there is no point writing something that someone else has already done better, so we will use the UserDetailsService from the Spring-Security-Core plugin in Grails 2.

grails-app/services/example/GormUserDetailsService

package example

import grails.transaction.Transactional
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority

@Transactional
class GormUserDetailsService implements UserDetailsService {

    @Transactional(readOnly = true, noRollbackFor = [IllegalArgumentException, UsernameNotFoundException])
    UserDetails loadUserByUsername(String username, boolean loadRoles) throws UsernameNotFoundException {

        def user = User.findWhere(username: username)
        if (!user) {
            log.warn "User not found: $username"
            throw new UsernameNotFoundException('User not found')
        }

        Collection authorities = loadAuthorities(user, username, loadRoles)
        createUserDetails user, authorities
    }

    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        loadUserByUsername username, true
    }

    protected Collection loadAuthorities(user, String username, boolean loadRoles) {
        if (!loadRoles) {
            return []
        }

        Collection userAuthorities = user.authorities
        def authorities = userAuthorities.collect { new SimpleGrantedAuthority(it.authority) }
        return authorities ?: [NO_ROLE]
    }

    protected UserDetails createUserDetails(user, Collection authorities) {
        new GrailsUser(user.username, user.password, user.enabled, !user.accountExpired, !user.credentialsExpired,
                !user.accountLocked, authorities, user.id)
    }
}  //Blogger now wants to close the GrantedAuthority 'tag', so 

Next, let's add a userDetailsService bean to resources.groovy for the GormUserDetailsService. Adding a bean to resources.groovy will take care of adding a Hibernate session and transaction to the service and allow us to use Gorm classes in the service.

grails-app/conf/spring/resources.groovy

import example.GormUserDetailsService
import simpleappwithsecurity.SecurityConfiguration

beans = {
    webSecurityConfiguration(SecurityConfiguration)
    userDetailsService(GormUserDetailsService)
}

Finally, let's replace the code for in-memory authentication:

grails-app/init/simpleappwithsecurity/SecurityConfiguration.groovy

    @Autowired
    protected void globalConfigure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser('user').password('user').roles('USER')
                .and()
                .withUser('admin').password('admin').roles('ADMIN');

with our shiny GormUserDetailsService

grails-app/init/simpleappwithsecurity/SecurityConfiguration.groovy

import org.springframework.security.core.userdetails.UserDetailsService;
    ...
    
    @Autowired
    UserDetailsService userDetailsService
    
    @Override  
    protected void configure(HttpSecurity http) throws Exception {  
        http  
            .authorizeRequests()  
                .antMatchers('/admin/**').hasAnyRole('ADMIN')  
                .antMatchers('/home/**').hasAnyRole('USER', 'ADMIN')  
                .antMatchers('/').permitAll()  
            .and()  
                .formLogin().permitAll()  
            .and()  
                .logout().permitAll()  
    }
    
    @Autowired
    protected void globalConfigure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
    }

Now fire up the app and you should be able to authenticate against the Gorm domain classes

Adding Password Encoding

The next post handles adding a password encoder (BCrypt) to the application

17 comments:

  1. how to configure requestmap grom

    ReplyDelete
    Replies
    1. You can use the same method from the first post to configure the request maps. I updated the post to include the request maps from the previous post. You can also take a look at the github repository (https://github.com/dspies/grails-3-with-security) to see the full working 'app'.

      Delete
  2. This solution looks like it exactly answers the Stackoverflow question I asked just a couple days prior to this blog post.
    Storing Spring Boot users in a database with Grails 3.0 (stackoverflow.com/q/30091373)

    Any chance of turning this solution into a Grails plugin?

    ReplyDelete
    Replies
    1. I posted an 'answer' to your stackoverflow question so that others having the same question have a jumping-off point. As for turning it into a Grails plugin, I have only been "playing" with Grails 3, and have not familiarized myself with the plugin architecture yet. I will look into what is required and see if I can get the discussion going on the Grails listserv(s).

      Delete
  3. Hi David,
    Thanks a lot for this github project. It made us up and running with Grails 3.0 and Spring Security. Though when we run generate-all command on example.User and try to save a user from create view we get the following message
    "Message: Expected CSRF token not found. Has your session expired?"
    Any thoughts?

    ReplyDelete
  4. Well, adding the <input type="hidden"
    name="${_csrf.parameterName}"
    value="${_csrf.token}"/> tag in every form solved the issue, but the same for meta tags <meta name="_csrf" content="${_csrf.token}"/>

    <meta name="_csrf_header" content="${_csrf.headerName}"/>

    won't work!
    Please refer to http://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-include-csrf-token
    may not hurt to add it in your blog

    ReplyDelete
    Replies
    1. Thanks, I'll add a section to the write-up when I get a chance.

      Delete
  5. Do you know how to configure this setup to use BCrypt for password encoding?

    ReplyDelete
    Replies
    1. Dem, I updated the github repo to handle password encoding (specifically BCrypt) and created a 3rd post for it http://spiesdavid.blogspot.com/2015/05/grails-3-app-with-security-part-3.html

      Delete
  6. Hallo David,

    thanks for this tutorial *thumbs up*

    Instead of using "@EnableWebSecurity" in SecurityConfiguration i would like to use "@EnableGlobalMethodSecurity(securedEnabled = true)" to have the "@Secured"-annotation for my controller actions. I found out, that i have to use the "@ComponentScan", when i want to use the "@Secured"-annotation. Do you know how to deal with "AccessDeniedException" when i use the annotation-way? Redirecting to login-page don't work anymore. (only exception is thrown)

    ReplyDelete
    Replies
    1. You have to add "ROLE_" to the role name (i.e. @Secured(value = ["ROLE_USER"]))
      I put a sample in my blog:
      http://noopeningbraceline.blogspot.fr/2015/07/grails-3-and-spring-boot-security-with.html

      Delete
    2. Thanks for the tip, but it doesn't work for me. In my case i use @Secured("ROLE_ADMIN"]) and when i logged in as an admin and request the secured method, than the request will be granted and i see my view. Thats great... When i do not logged in as an admin and request the secured method than an AccessDeniedException is thrown. So...spring security does what it should do but i expect to have a redirect to login page. When i use url-interceptors and i'm not logged in than i will be redirected to /login. Do you know how to customize the behaviour when i use method security level instead of url interceptors?

      Delete
  7. Hi David,

    First of all thanks for the tutorial. I'm new to Grails and it seems that the newest Grails version 3.0 is also quite new and many thing like old plugins from 2.x doesn't work with Grails 3.0.

    I'm having some problems with the GormUserDetailsService.groovy class. How can you use
    new GrailsUser(user.username, user.password, user.enabled, !user.accountExpired, !user.credentialsExpired, !user.accountLocked, authorities, user.id)

    in createUserDetails method when you've not imported the GrailsUser.java class? I also checked it from Github but there's no import either. It gives an error without the import and when I import the GrailsUser.java class it won't compile.

    ReplyDelete
  8. Hi David,

    I was just wondering how can i customize the default login page to my liking?
    I tried to follow this from stackoverflow, but it won't work on Grails 3.0 because it needs the core plugin which doesn't work.

    Nice tutorials keep up the good work

    ReplyDelete
    Replies
    1. Anyone have any handy pointers on how to customise the login page?

      Delete
  9. Hi David,

    I was just wondering how can i customize the default login page to my liking?
    I tried to follow this from stackoverflow, but it won't work on Grails 3.0 because it needs the core plugin which doesn't work.

    Nice tutorials keep up the good work

    ReplyDelete
  10. Hi great tutorial, but i'm having an issues all my AJAX requests to a controller are getting 403 forbidden, i'm very sure that i'm authenticated in the moment of the request

    ReplyDelete