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.
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:
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
Could you advise what tool you are using while you test the http post form?
Sure Jf li. I use PAW ( https://paw.cloud )
wonderful! thanks a lot
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 🙁
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.
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
Thank you very much for this wonderful article. If you are issuing a rest call from an application instead of through a browser, what is the best way to accomplish the authentication?
It is very useful!
How is possibile to simply save access login into a database? I have tried to register an URI (/api/login) interceptor to store authentication tries, but it seems /api/login is not intercepteable…