Grails Part 5 - Adding Basic Security

Click here to view the full tutorial

Continuing series: Developing a Grails Web Application.
Tutorial will: walk through steps of creating a robust Grails web application.
Application name: grails-mongo-demo
The Goal: To track and record persons addresses.
This application will use the Grails Framework to develop a web application using:
1. MongoDB
2. Spring Security
3. Apache CXF
4. Jetty Web Server
5. Maven build integration => Removed upon Request
Previous Post: Grails Part 4 - Enhanced UI Design w/ tw Bootstrap
View Project on Github

These Instructions Will Walk Through:

  • Configuring Spring Security Plugin
  • Adding static URL maps
  • Bootstrapping in security data
  • SecurityTagLib - Conditionally display gsp content
  • Configure Security Pages with Twitter Bootstrap UI
If you would like to follow along but haven't walked through each tutorial, downoad this tag on github. This is a snapshot of the demo project before adding security. To clone from git, use these commmands:
    $ git clone https://github.com/keaplogik/grails-mongodb-demo.git
    $ cd grails-mongodb-demo/
    $ git checkout v1.0

1. Configuring Spring Security Plugin

To start, ensure you have the spring-security-core plugin installed. If not, add it to your POM if using maven, otherwise add it to the dependency list of the BuildConfig class. If following along, with the grails-mongodb-demo, you will already have it installed.
A useful command s2-quickstart is built into the security plugin. It generates the controllers to handle authentication, as well the domain classes you'll need to store user information.
As in the springsource blog, we will create domain classes with names: SecUser and SecRole.
Also generated:
  • SecUserSecRole - links users to roles.
  • LoginController
  • LogoutController
  • Associated login/logout views
So start by runnning:
    $ grails s2-quickstart org.mycompany SecUser SecRole
The application now needs to specify mappings to the login/logout controllers. Add the following to the configuration class UrlMappings:
    "/login/$action?"(controller: "login")
    "/logout/$action?"(controller: "logout")
There are three ways you can configure security.
You can take a controller-centric approach and annotate the actions; work with static URL rules in Config.groovy; or define runtime rules in the database using request maps.
Consult the springsource blog post Simplified Spring Security with Grails for more information.

2. Adding static URL maps

For the grails-mongodb-demo, we will secure the application using static URL rules.
The Rules:
1. Any authenticated user has access to the Person Index
2. Only Admin's can create/update/delete Persons or Addresses
To enable, set the securityConfigType and add interceptUrlMap Configurations to the Config.groovy class:
grails.plugins.springsecurity.securityConfigType = 'InterceptUrlMap'
grails.plugins.springsecurity.interceptUrlMap = [
        '/person/index':    ['ROLE_USER, ROLE_ADMIN, IS_AUTHENTICATED_FULLY'],
        '/person/**':       ['ROLE_ADMIN'],
        '/address/**':      ['ROLE_ADMIN'],
        '/js/**':           ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/css/**':          ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/images/**':       ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/*':               ['IS_AUTHENTICATED_FULLY'],
        '/login/**':        ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/logout/**':       ['IS_AUTHENTICATED_ANONYMOUSLY']

]
Optional security configurations:
grails.plugins.springsecurity.password.algorithm='SHA-512'      //pw encryption algorithm 
grails.plugins.springsecurity.portMapper.httpPort = "8080"      //port map for http
grails.plugins.springsecurity.portMapper.httpsPort = "8443"     //port map for https
grails.plugins.springsecurity.rejectIfNoRule = true             //force authentication if no rule exists

3. Bootstrap in security data

Our application needs to determine who has access to what URL. Because of this, we need to create a set of rules. They only need to be created once on application start, so lets use Bootstrap to create them.
class BootStrap {
    def init = {
        ...
        def userRole = SecRole.findByAuthority('ROLE_USER') ?: new SecRole(authority: 'ROLE_USER').save(failOnError: true)
        def adminRole = SecRole.findByAuthority('ROLE_ADMIN') ?: new SecRole(authority: 'ROLE_ADMIN').save(failOnError: true)
        ...
    }
}
Now configure two users; one with admin privilege and, one with default user privilege.
  • Admin User {u/p}: {admin/admin}
  • Default User {u/p}: {guest/guest}
In the Bootstrap.init{...}, add persistence for the default users :
    //add an admin and default user
    def adminUser = SecUser.findByUsername('admin') ?: new SecUser(
            username: 'admin',
            password: 'admin',
            enabled: true).save(failOnError: true)

    def basicUser = SecUser.findByUsername('guest') ?: new SecUser(
            username: 'guest',
            password: 'guest',                          //pw encoded by security plugin
            enabled: true).save(failOnError: true)

    if (!adminUser.authorities.contains(adminRole)) {
        SecUserSecRole.create adminUser, adminRole
    }
    if (!basicUser.authorities.contains(userRole)) {
        SecUserSecRole.create basicUser, userRole
    }

4. SecurityTagLib - Conditionally display gsp content

The security tags are part of the sec namespace, and can be used in your gsp's. Heres an example:
<sec:ifLoggedIn>
    <p>Your Logged in!</p>
</sec:ifLoggedIn>
We are going to use the security tag lib, to hide edit/create options on the person/index page. Specifically the condition will check if a user has the role: ROLE_ADMIN:
<sec:ifAllGranted roles="ROLE_ADMIN">hide this stuff</sec:ifAllGranted>
To view a list of tags and their explanations see the Grails SecurityTagLib Documentation.
Go ahead and open up the view grails-mongodb-demo/grails-app/views/person/index.gsp.
Wrap the Create New Person link like so:
<sec:ifAllGranted roles="ROLE_ADMIN">
<g:link class="btn btn-block btn-link" action="create">
    Create New Person
</g:link>
</sec:ifAllGranted>
And now the table data containing the options column, along with the button group:
<sec:ifAllGranted roles="ROLE_ADMIN">
<th>Options</th>
</sec:ifAllGranted>

...

<sec:ifAllGranted roles="ROLE_ADMIN">
<td><g:link class="btn btn-small btn-inverse" controller="address"
            action="edit" id="${address.id}">
        <i class="icon-edit icon-white"></i>
    </g:link>
</td>

...

<sec:ifAllGranted roles="ROLE_ADMIN">
<div class="btn-group">
    <g:link class="btn btn-primary" action="edit" id="${person.id}">
        <i class="icon-edit icon-white"></i>Edit
    </g:link>
</div>
</sec:ifAllGranted>
</sec:ifAllGranted>

5. Configure Security Pages with Bootstrap UI

We want to give the user an option to log out from any screen. Let's use a static navbar in our /layouts/main.gsp:
<body>
    <div class="navbar navbar-static-top">
        <div class="navbar-inner">
            <div class="container">
                <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </a>
                <g:link class="brand" uri="/">Address Tracker</g:link>
                <div class="nav-collapse">
                    <ul class="nav pull-right">
                        <sec:ifLoggedIn>
                        <li>
                            <g:link controller="logout" action="index">Logout</g:link>
                        </li>
                        </sec:ifLoggedIn>
                    </ul>
                </div>
            </div>
        </div>
    </div>
    <g:layoutBody/>
    ...
My auth.gsp page configured with twitter bootstrap is below, but you can modify yours however.
<html>
<head>
    <meta name='layout' content='main'/>
    <r:require modules="bootstrap"/>
    <title><g:message code="springSecurity.login.title"/></title>
</head>
<body>
    <div class='container'>
            <g:if test='${flash.message}'>
                <div class='alert alert-error'>${flash.message}</div>
            </g:if>
            <div class="row span12">
                <legend><g:message code="springSecurity.login.header"/></legend>
                <div class="span6">
                    <form action='${postUrl}' method='POST' id='loginForm' class='form-horizontal' autocomplete='off'>
                        <div class="control-group">
                            <label class="control-label" for='username'><g:message code="springSecurity.login.username.label"/>:</label>
                            <div class="controls">
                                <input type='text' class='text_' name='j_username' id='username'/>
                            </div>
                        </div>
                        <div class="control-group">
                            <label class="control-label" for='password'><g:message code="springSecurity.login.password.label"/>:</label>
                            <div class="controls">
                                <input type='password' class='text_' name='j_password' id='password'/>
                            </div>
                        </div>
                        <div class="control-group" id="remember_me_holder">
                            <div class="controls">
                                <label class="checkbox" for='remember_me'><g:message code="springSecurity.login.remember.me.label"/>
                                    <input type='checkbox' name='${rememberMeParameter}' id='remember_me'
                                           <g:if test='${hasCookie}'>checked='checked'</g:if>/>
                                </label>
                                <input class="btn btn-primary" type='submit' id="submit" value='${message(code: "springSecurity.login.button")}'/>
                            </div>
                        </div>
                    </form>
                </div>
                <div class="span4">
                    <div class="">
                        <dl class="dl-horizontal">
                            <dt>Admin User (u/p):</dt>
                            <dd>admin/admin</dd>
                        </dl>
                        <dl class="dl-horizontal">
                            <dt>Guest User (u/p):</dt>
                            <dd>guest/guest</dd>
                        </dl>
                    </div>
                </div>
            </div>
    </div>
</body>
</html>

And the denied.gsp:
<head>
    <meta name='layout' content='main' />
    <r:require modules="bootstrap"/>
    <title><g:message code="springSecurity.denied.title" /></title>
</head>

<body>
    <div class='container'>
        <div class='alert alert-block alert-error'>
            <g:message code="springSecurity.denied.message" />
            <br/><br/>
            <g:link class="btn btn-inverse" uri="/">Return Home</g:link>
        </div>
    </div>
</body>

The application is now setup with basic spring security.
You can find a list of additional plugins with advanced configurations, on the spring security plugin page.

Click here to view the full tutorial

Comments

  1. When a service is exposed as soap by using cxf, can that be secured in a way?

    ReplyDelete

Post a Comment

Popular posts from this blog

Atmosphere Websockets & Comet with Spring MVC

Microservices Tech Stack with Spring and Vert.X