How to secure Rest Api with spring security rest plugin

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

At this point, we have a working API, but it is not secure, any one can access the API. Now lets secure it.

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

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":"eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTUFU4Y01SQ2RYUTRSaEVRZ1VpSlJrQ2FraVwvYWtVRjRWVHBkSTBRb1FsMnRBQ3ZMdERvdkJhMjlzTDl3MTBWVlFVSUQ0a0tMa0xcL0JQb09FSElDaG9xV2taTHh4N1NZUGl5aDRcL3ZcL2ZtalU5dllkaG9tRTAwNDhJRW1jZ1RMZ09UYVM0VGcxR3V1ZTBHdVVFZG95MFFYd3BnaXlyd3NEd2Z2QkI4SGx0NEZXNndMVllWVENiVmhmWUdScmJXMGZCUjZlU1JjVTJ6RkxlVjNneWV1Q09sOFMrQmt0cjc3Y1BJTWt5eUtGSzV0UE5LTmpvWjF4Z3Z3MFJaQzFXMDZVcXZJN3BCYVRrVFpoQTZncEsxQmNZaGpMSGNyaXRTNVdnc3ZId3dtMXN1cWsyMHRSQmVaTXdZY3ZkUEowM3JyTHQ3WjFOU0J6XC9nSjFRNm1VZUxzbnZ2b0lIakNlcEtDT3FhSzJsbVdqSlZNVlwvalRwejRlOU9IRlwvdFwvZWkwZmdETDU4UHlic2o0MUI3Mno3M2R2aTZDOXlNS2JBZXNsck5iSnlNMWt5ZnhObzFPK1wvTFY0ZEhLN3V6SkV5Zzd4K2ZcL25NZlBwTWJsdVhhVVowOHlxZ1JrUjdYYkY3WWw4N25ueVwvaFM2UVpPbm1VRDZVZEppXC9DUlJFbE83RmExRVAyOExvMHNMWVdPMTFXd3N1Wk9mSWltT0Z3MjdTUVdob2pudDNSeWM3Nys3b3RkZllYaUxpUndwNzRrU05KK25iZFE3cHlmVFk4ZlhlNFg3XC9rKytCOUVYXC9DSU5Bd0FBIiwic3ViIjoibWUiLCJyb2xlcyI6WyJST0xFX1VTRVIiXSwiZXhwIjoxNTAyMTEyOTMxLCJpYXQiOjE1MDIxMDkzMzF9.RlNX3-2SxpoPGkmvJ5VRspn2l_vjeU1kxXgmX1sOlzU","expires_in":3600,"refresh_token":"eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTUFU4Y01SQ2RYUTRSaEVRZ1VpSlJrQ2FraVwvYWtVRjRWVHBkSTBRb1FsMnRBQ3ZMdERvdkJhMjlzTDl3MTBWVlFVSUQ0a0tMa0xcL0JQb09FSElDaG9xV2taTHh4N1NZUGl5aDRcL3ZcL2ZtalU5dllkaG9tRTAwNDhJRW1jZ1RMZ09UYVM0VGcxR3V1ZTBHdVVFZG95MFFYd3BnaXlyd3NEd2Z2QkI4SGx0NEZXNndMVllWVENiVmhmWUdScmJXMGZCUjZlU1JjVTJ6RkxlVjNneWV1Q09sOFMrQmt0cjc3Y1BJTWt5eUtGSzV0UE5LTmpvWjF4Z3Z3MFJaQzFXMDZVcXZJN3BCYVRrVFpoQTZncEsxQmNZaGpMSGNyaXRTNVdnc3ZId3dtMXN1cWsyMHRSQmVaTXdZY3ZkUEowM3JyTHQ3WjFOU0J6XC9nSjFRNm1VZUxzbnZ2b0lIakNlcEtDT3FhSzJsbVdqSlZNVlwvalRwejRlOU9IRlwvdFwvZWkwZmdETDU4UHlic2o0MUI3Mno3M2R2aTZDOXlNS2JBZXNsck5iSnlNMWt5ZnhObzFPK1wvTFY0ZEhLN3V6SkV5Zzd4K2ZcL25NZlBwTWJsdVhhVVowOHlxZ1JrUjdYYkY3WWw4N25ueVwvaFM2UVpPbm1VRDZVZEppXC9DUlJFbE83RmExRVAyOExvMHNMWVdPMTFXd3N1Wk9mSWltT0Z3MjdTUVdob2pudDNSeWM3Nys3b3RkZllYaUxpUndwNzRrU05KK25iZFE3cHlmVFk4ZlhlNFg3XC9rKytCOUVYXC9DSU5Bd0FBIiwic3ViIjoibWUiLCJyb2xlcyI6WyJST0xFX1VTRVIiXSwiaWF0IjoxNTAyMTA5MzMxfQ.lJp_gxG0Jld1WMtw2WeasZ88LXZIsk4j5WkFfI7HL7k"}

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 eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTUFU4Y01SQ2RYUTRSaEVRVpSlJrQ2FraVwvYWtVRjRWVHBkSTBRb1FsMnRBQ3ZMdERvdkJhMjlzTDl3MTBWVlFVSUQ0a0tMa0xcL0JQb09FSElDaG9xV2taTHh4N1NZUGl5aDRcL3ZcL2ZtalU5dllkaG9tRTAwNDhJRW1jZ1RMZ09UYVM0VGcxR3V1ZTBHdVVFZG95MFFYd3BnaXlyd3NEd2Z2QkI4SGx0NEZXNndMVllWVENiVmhmWUdScmJXMGZCUjZlU1JjVTJ6RkxlVjNneWV1Q09sOFMrQmt0cjc3Y1BJTWt5eUtGSzV0UE5LTmpvWjF4Z3Z3MFJaQzFXMDZVcXZJN3BCYVRrVFpoQTZncEsxQmNZaGpMSGNyaXRTNVdnc3ZId3dtMXN1cWsyMHRSQmVaTXdZY3ZkUEowM3JyTHQ3WjFOU0J6XC9nSjFRNm1VZUxzbnZ2b0lIakNlcEtDT3FhSzJsbVdqSlZNVlwvalRwejRlOU9IRlwvdFwvZWkwZmdETDU4UHlic2o0MUI3Mno3M2R2aTZDOXlNS2JBZXNsck5iSnlNMWt5ZnhObzFPK1wvTFY0ZEhLN3V6SkV5Zzd4K2ZcL25NZlBwTWJsdVhhVVowOHlxZ1JrUjdYYkY3WWw4N25ueVwvaFM2UVpPbm1VRDZVZEppXC9DUlJFbE83RmExRVAyOExvMHNMWVdPMTFXd3N1Wk9mSWltT0Z3MjdTUVdob2pudDNSeWM3Nys3b3RkZllYaUxpUndwNzRrU05KK25iZFE3cHlmVFk4ZlhlNFg3XC9rKytCOUVYXC9DSU5Bd0FBIiwic3ViIjoibWUiLCJyb2xlcyI6WyJST0xFX1VTRVIiXSwiZXhwIjoxNTAyMTEyOTMxLCJpYXQiOjE1MDIxMDkzMzF9.RlNX3-2SxpoPGkmvJ5VRspn2l_vjeU1kxXgmX1sOlzU" 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.