Grails Programmer : How to secure your Grails 3 API with Spring Security REST for Grails?

For this post I will continue reusing the code I created in the How to use Spring Security Core to Secure your Grails 3 app post

In this post we are going to expose our app product announcements as JSON API and secure it with Spring Security REST for Grails plugin.

Lets create a service to encapsulate the logic.

$ grails create-service ProductAnnouncement 
| Created grails-app/services/myapp/ProductAnnouncementService.groovy
| Created src/test/groovy/myapp/ProductAnnouncementServiceSpec.groovy

with the content:


package myapp

import grails.transaction.Transactional

@Transactional
class ProductAnnoucementService {

    List<ProductAnnouncement> lastAnnouncements() {

        ProductAnnouncement.createCriteria().list {
    	    order("dateCreated", "desc")
            maxResults(1)
        }

    }
}


Lets create a controller for our API endpoints.

$ grails create-controller Api
| Created grails-app/controllers/myapp/ApiController.groovy
| Created src/test/groovy/myapp/ApiControllerSpec.groovy

Lets define a controller action to expose the last announcements as a JSON payload

package myapp

import grails.plugin.springsecurity.annotation.Secured

class ApiController {

    def productAnnoucementService

    @Secured(['ROLE_USER'])
    def announcements() {
        render(contentType: "application/json") {
            announcements {
                for(a in productAnnoucementService.lastAnnouncements()) {
                    announcement(message: a.message)
                }
            }
        }
    }
}

Install Spring Security REST for Grails

To install it we need to add a dependency to build.gradle

buildscript {
    ext {
        grailsVersion = project.grailsVersion
    }
    repositories {
        mavenLocal()
        maven { url "https://repo.grails.org/grails/core" }
    }
    dependencies {
        classpath "org.grails:grails-gradle-plugin:$grailsVersion"
        classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.5.0"
        classpath "org.grails.plugins:hibernate4:5.0.0"
    }
}

version "0.1"
group "myapp"

apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"org.grails.grails-gsp"
apply plugin:"asset-pipeline"

ext {
    grailsVersion = project.grailsVersion
    gradleWrapperVersion = project.gradleWrapperVersion
}

repositories {
    mavenLocal()
    maven { url "https://repo.grails.org/grails/core" }
}

dependencyManagement {
    imports {
        mavenBom "org.grails:grails-bom:$grailsVersion"
    }
    applyMavenExclusions false
}

dependencies {
    compile "org.springframework.boot:spring-boot-starter-logging"
    compile "org.springframework.boot:spring-boot-autoconfigure"
    compile "org.grails:grails-core"
    compile "org.springframework.boot:spring-boot-starter-actuator"
    compile "org.springframework.boot:spring-boot-starter-tomcat"
    compile "org.grails:grails-dependencies"
    compile "org.grails:grails-web-boot"
    compile "org.grails.plugins:cache"
    compile "org.grails.plugins:scaffolding"
    compile "org.grails.plugins:hibernate4"
    compile "org.hibernate:hibernate-ehcache"
    console "org.grails:grails-console"
    profile "org.grails.profiles:web:3.1.1"
    runtime "org.grails.plugins:asset-pipeline"
    runtime "com.h2database:h2"
    testCompile "org.grails:grails-plugin-testing"
    testCompile "org.grails.plugins:geb"
    testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
    testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
    compile 'org.grails.plugins:spring-security-core:3.0.3'
    compile "org.grails.plugins:spring-security-rest:2.0.0.M2"
}

task wrapper(type: Wrapper) {
    gradleVersion = gradleWrapperVersion
}

assets {
    minifyJs = true
    minifyCss = true
}

Spring Security REST for Grails exposes an endpoint /api/login which we can access with a POST request. We should supply our credentials (username and password) as parameters. If we are successfully authenticated we will get an access_token and a refresh_token.

Untitled_Paw_Document

We can do a GET request to our http://localhost:8080/api/announcements endpoint. We need to add an HTTP header:
Header Name: Authorization
Header Value: Bearer access_token

The access_token is the one we got back from the api/login endpoint.

The above request is illustrated here:

Note were are using a JSON Web Token (JWT) which we don’t need to store on the server side. The token validation is done without a repository (database or similar) call. Thus, better for the scalability of the application.

Access token expire. However in the api/login method we got a refresh token which we can use to get a new access token. We can do POST request as shown below:

Oauth Refresh token

Do you like to read about Groovy/Grails development? Yes, then Subscribe to Groovy Calamari a weekly curated email newsletter about the Groovy ecosystem which I write 

6 thoughts on “Grails Programmer : How to secure your Grails 3 API with Spring Security REST for Grails?

  1. Farah

    What if I want to secure the “def announcements()” for 2 specific users? How can I do that?
    I have tried so far….
    @Secured([“authentication.name == ‘admin1′”, “authentication.name == ‘admin2′”])
    @Secured(“authentication.name in [‘admin1’, ‘admin2’]”)
    @Secured(“[‘admin1′,’admin2’].contains(authentication.name)”)

    None of them are working 🙁

    Reply
  2. Sergio

    Farah I believe it is a closure.

    That it is to say:

    @Secured( { [‘admin1’, ‘admin2’].contains(authentication.name) })

    Note I think your best option would be to add a role to admin1 and admin2 and secured the action based on that role.

    Reply
  3. Ibrahim H.

    Hi Sergio,
    Thank you for this great tutorial, there is just a small typo when you wrote “productAnnoucementService” but your called the service “ProductAnnouncement” (so the file would be: grails-app/services/myapp/ProductAnnouncementService.groovy), this cause the api controller to query on null object and I’ve spent sometime to discover that, but thank you anyway 🙂
    Regards

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *