Spring security framework has become a De facto standard for security web applications. However integrating the springsecurity into an application be quite a daunting task. Luckily there is a Grails spring security core plugin which makes it very easy to integrate the spring security framework in any grails application.
Spring security core plugin provides form based authentication out of the box. However form based authentication is not suitable for Rest API. There is another Grails plugin spring security rest which builds on top of spring security core plugin and provides the capability of token based authentication.
In this post I’ll show how to use spring security rest plugin to secure Rest API. The example project can be found on github . We will create a simple rest api and see how to secure it.
Create an application
If you have an existing application, you can leave this step, otherwise create a new application.
grails create-app spring-security-rest-sample
Create a domain class
class Book {
String name
String isbn
BigDecimal price
}
Populate intial data
File: grails-app/init/BootStrap.groovy
def init = { servletContext ->
Book.withTransaction {
Book book = new Book(name: "Grails in action", isbn: "12345", price: 30.00).save(failOnError:true)
}
}
Create A Rest API
Lets create a simple Rest API for our book domain.
Grails makes it very easy to build Rest API. If you are not already familiar with it, you should read the Rest API part of Grails documentationm, and Restful Url Mappings
Create controller for book api
Create a new controller that extends the RestFulController.
class BookController extends RestfulController<Book>{
BookController() {
super(Book)
}
}
Url mapping for book api
"/api/book"(resources: "book")
At this point you should have a working Rest API for the book domain. You can test it with your favourite Rest Client. But we will use Just curl. Run the following command from the console, the api should return list of books as json. We have created just one book record from BootStrap.groovy so the list will contain a single record.
curl -H "accept:application/json" http://localhost/api/book
Install spring security core plugin
File: build.gradle
compile 'org.grails.plugins:spring-security-core:3.1.2'
Create User and Role domain classes
Run following command, it will create User, Role and UserRole domain classes. It will also populate some settings in application.groovy file. You can change the package name to whatever you want. But if you do, make sure you change the same in application.groovy file too.
grails s2-quickstart me.nimavat User Role
At this point, we have a working API, but it is not secure, any one can access the API. Now lets secure it.
Secure the BookController
Lets say, we want BookController to be accessible to those users who has Role Role_USER. Modify the BookController and add the @Secured
annotation.
@Secured("ROLE_USER")
class BookController extends RestfulController<Book>{
BookController() {
super(Book)
}
}
Now, if you are not logged in, and if you try to access the http://localhost:8080/api/book
from browser or rest client, you will notice that the login form is returned. But that’s not what we want. When unauthorized, the Rest API should return a json response with proper status code and error message. We need to install spring security rest plugin for that.
Install spring security rest plugin
File build.gradle
compile "org.grails.plugins:spring-security-rest:2.0.0.M2"
Configure the spring security filter chain
Filter chain needs to be configured to include the spring security rest related filters to involve the spring security rest plugin into authentication process.
File: application.groovy
grails.plugin.springsecurity.filterChain.chainMap = [
//unsecured
[pattern: '/assets/**', filters: 'none'],
[pattern: '/**/js/**', filters: 'none'],
[pattern: '/**/css/**', filters: 'none'],
[pattern: '/**/images/**', filters: 'none'],
[pattern: '/**/favicon.ico', filters: 'none'],
//Stateless chain for REST API
[
pattern: '/api/**',
filters: 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter'
],
//Traditional chain for form based login
[
pattern: '/**',
filters: 'JOINED_FILTERS,-restTokenValidationFilter,-restExceptionTranslationFilter'
]
]
**Note**: the pattern: '/api/**'. This matches our url mapping /api/book. The rest authentication will apply just to those urls which are under /api/. If you need you can configure multiple filter chanins for rest. Just copy the existing one and change the pattern.
At this point, the API should be secured. If you try to access the url api/book you should get unauthorized response.
curl -H "accept:application/json" http://localhost:8080/api/book
Response
{"timestamp":1502108972054,"status":401,"error":"Unauthorized","message":"No message available","path":"/api/book"}
Login with API
Add a test user which we will use for the login.
File: BootStrap.groovy
def init = { servletContext ->
User.withTransaction {
Book book = new Book(name: "Grails in action", isbn: "12345", price: 30.00).save(failOnError:true)
Role role = new Role(authority: "ROLE_USER").save(failOnError:true)
User user = new User(username: "me", password: "password").save(failOnError:true)
UserRole.create(user, role)
}
}
By default the login url for api is api/login. The url expects a json with fields username and password.
curl -X POST -H "Accept:application/json;" -H "Content-Type:application/json" -d '{"username":"me", "password":"password"}' http://localhost:8080/api/login
{"username":"me","roles":["ROLE_USER"],"token_type":"Bearer","access_token":"XXX"}
Upon successful login the api returns a json response with an authentication token. The authentication token can be used to make the further requests.
The consumer of the API can store the token and pass it in header for making further requests to API.
Spring security rest plugin supports multiple storage mechanism, the default is JWT. By default the the plugin uses Bearer token.
Make request with authentication token.
By default the plugin expects a header with name Authorization with bearer token.
curl -H "accept:application/json" -H "Authorization: Bearer XXX" http://localhost:8080/api/book
Copy the token value from the response returned from api/login and pass it as Authorization header as shown above. This will result in the request getting successfully authenticated. And the api would return the response instead of returning unauthorized error.
How to change the name of token header.
Lets disable the Bearer token and change the name of authentication header to X-Auth-Token
File: application.groovy
grails.plugin.springsecurity.rest.token.validation.useBearerToken = false
grails.plugin.springsecurity.rest.token.validation.headerName = 'X-Auth-Token'
Now the requests can be made as shown below.
curl -H "accept:application/json" -H "X-Auth-Token:XXX" http://localhost:8080/api/book
Enable the Gorm Storage
By default the plugin uses The JWT tokens. With jwt no state is stored on the server side. There are plugins which provides alternative storage mechanism such as Gorm, Memcached or Grails cache. Lets see how to use the Gorm storage.
Install the spring security rest gorm plugin.
compile "org.grails.plugins:spring-security-rest-gorm:2.0.0.M2"
When using the Gorm storage, the token is stored in database, and needs a domain class. Create a domain class as shown below.
package me.nimavat
class RestAuthenticationtoken {
String token
String username
Date dateCreated
static mapping = {
version false
}
static constraints = {
dateCreated nullable: true
}
}
Configure the spring security rest plugin to enable the gorm storage and use the domain class.
File: application.properties
grails.plugin.springsecurity.rest.token.storage.useGorm = true
grails.plugin.springsecurity.rest.token.storage.gorm.tokenDomainClassName = "me.nimavat.RestAuthenticationtoken"
grails.plugin.springsecurity.rest.token.storage.gorm.tokenValuePropertyName = "token"
Now if you send a login request to api/login the plugin will create a new token and store it in database. The token can be used to authenticate the rest api requests as explained earlier.
How to implement Logout
When using JWT the token is not stored on the server, so the way to logout is to delete the token from client side. However if you are using the Gorm or other storage mechanism the logout can be implemented by deleting the token record.
The plugin provides a LogoutFilter to support the logout functionality however there's a bug and the filter is not registered in spring security filter chain. So api/logout endpoint will not work. You have to implement your own controller or filter to implement the logout functionality.
How to expire the tokens
We are already storing the dateCreated in the RestAuthenticationToken domain class. So you can create a quartz job that will delete all tokens which were created before some specific days.