Saturday, June 25, 2016

Create a Secured Restful API App with Grails 3, PostgreSQL and JMS - Step by Step: Part 2 of 5

Part 2: Add Core Security and REST API Security 

In Part 1, I created a Grails 3 application using rest-api profile. In this part, I will list steps to add security. The Grails Spring Security Core plugin is one of the very popular Grails plugins and when Grails 3 was released, this was the most desired plugin by the Grails community to be migrated to Grails 3. Thanks to Burt Beckwith!

Security

Typically, web application's resources (pages/urls) are secured through a login form. Spring Security framework is a very flexible framework and meets most os web application's security needs. Though it simplifies many aspects of security, it comes with some level of complexities.

In Grails world, the popular Grails Spring Security Core plugin which underpins Spring Security makes security trivial. However, the core plugin is not just enough for securing RESTful Grails application. In a RESTful application, all end-points need to be secured and a stateless/session-less mechanism for safely accepting HTTP requests through HTTP methods to urls should be in place. The Grails Spring Security REST plugin which is written on top of Grails Spring Security Core plugin makes this possible in a simple manner. It supports token-based RESTful authentication. It also supports JSON Web Token (JWT).

Let's try some hands on...

Environment: Grails 3.1.6, Java 1.8, IntelliJ 15 on Mac OS X 10.9.5

Before I start adding Security, I will import the grails app created into my IntelliJ IDEA. IntelliJ 15 supports Grails 3. Also, Grails 3 switched it's build system from GANT to Gradle. So the Grails 3 app created can easily be imported as a Gradle project. IntelliJ recognizes it as a Grails project and adds all needed support.

To import into IntelliJ IDEA 15, simply follow these steps:

1. Open IntelliJ IDEA and go to File > New > Project from existing sources... 2. Select build.gradle file under the grails3 app created (in my example it's giri-api) 3. IntelliJ recognizes it as a Gradle project. Go with default options in the next step. If multiple JVMs are available in IntelliJ select the one you prefer for Gradle JVM. I have 1.6, 1.7 and 1.8 and I selected 1.8. Press OK. 4. IntelliJ recognizes it as Gradle project, imports and builds the project. It also recognizes the project dir as root module and prompts for modules. Just press OK. 5. It imports the project and prompts for Grails shell. Click the link 'Select Grails SDK Home'. I have multiple Grails versions installed and I select  ~/.gvm/grails/3.1.6 6. If you miss step-5 due to any reason, you can add Grails SDK Home from: File > Other Settings > Default Settings > Languages & Frameworks > Grails

Also, notice that IntelliJ adds a run configuration with the project name to run the main Application.groovy class. This lets you run the grails application from IntelliJ. Just click Run > Edit Configurations menu item or the drop down from the run configurations icon and take a look at the configuration. You can add more run configurations and setup various grails commands to run directly from IntelliJ.

Step 1 Add Security - Grails Spring security core plugin dependency to the application's build.gradle file

dependencies { ... //grails security-core plugin compile "org.grails.plugins:spring-security-core:3.1.0" ... }

Step 2 Generate security related classes quickly

This step is documented well in the quick start section of the plugin documentation.

Minimally, we need User and Role domain classes for security. As domain classes have equivalent persistent db tables created by Grails, some databases wouldn't allow to have tables with names that are reserved words. At least, PostgresSQL will not let a table named User. So we I will use AppUser instead.

From the project home directory execute the following:
$ grails s2-quickstart com.giri.security AppUser Role BUILD SUCCESSFUL | Creating User class 'AppUser' and Role class 'Role' in package 'com.giri.security' | Rendered template Person.groovy.template to destination grails-app/domain/com/giri/security/AppUser.groovy | Rendered template Authority.groovy.template to destination grails-app/domain/com/giri/security/Role.groovy | Rendered template PersonAuthority.groovy.template to destination grails-app/domain/com/giri/security/AppUserRole.groovy | ************************************************************ * Created security-related domain classes. Your * * grails-app/conf/application.groovy has been updated with * * the class names of the configured domain classes; * * please verify that the values are correct. * ************************************************************

The above command generates 3 classes: AppUser.groovy, Role.groovy and AppUserRoles.groovy under grails-app/domain/com/giri/security directory.

Also, take a look at application.groovy. It will have security plugin's default properties added. Some of these properties and values are obvious. We don't need to change anything at this time. Just make a note of grails.plugin.springsecurity.filterChain.chainMap, this needs some attention later when we secure rest end points using annotations.
// Added by the Spring Security Core plugin: grails.plugin.springsecurity.userLookup.userDomainClassName = 'com.giri.security.AppUser' grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'com.giri.security.AppUserRole' grails.plugin.springsecurity.authority.className = 'com.giri.security.Role' grails.plugin.springsecurity.controllerAnnotations.staticRules = [ [pattern: '/', access: ['permitAll']], [pattern: '/error', access: ['permitAll']], [pattern: '/index', access: ['permitAll']], [pattern: '/index.gsp', access: ['permitAll']], [pattern: '/shutdown', access: ['permitAll']], [pattern: '/assets/**', access: ['permitAll']], [pattern: '/**/js/**', access: ['permitAll']], [pattern: '/**/css/**', access: ['permitAll']], [pattern: '/**/images/**', access: ['permitAll']], [pattern: '/**/favicon.ico', access: ['permitAll']] ] grails.plugin.springsecurity.filterChain.chainMap = [ [pattern: '/assets/**', filters: 'none'], [pattern: '/**/js/**', filters: 'none'], [pattern: '/**/css/**', filters: 'none'], [pattern: '/**/images/**', filters: 'none'], [pattern: '/**/favicon.ico', filters: 'none'], [pattern: '/**', filters: 'JOINED_FILTERS'] ]

Gotcha

When I ran s2-quickstart command for generating security domain classes, I passed package com.giri.security explicitly. Otherwise, grails uses the default package and the defaultPackage is defined in application.yml under elements grails: codegen: Out-of-the-box, grails uses application name as the default package with hiphens (-) converted to dots (.), if hiphens are used in the application name.  My application name is giri-api and hence when I created the app the defaultPackage was set to giri.api in application.yml. You can change it to the default package you would like to have. Once changed, any grails artifacts generated using grails create-xxxxx commands use this as the default package and generate all classes under that package. I changed the default to com.giri for convenience.

Step 3 Run the app

$ grails run-app Configuring Spring Security Core ... ... finished configuring Spring Security Core Grails application running at http://localhost:8080 in environment: development

Point your browser at: http://localhost:8080
It will return {"message":"Internal server error","error":500}
Check the console, you will see:
ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[grailsDispatcherServlet] - Servlet.service() for servlet [grailsDispatcherServlet] in context with path [] threw exception [Could not resolve view with name '/login/auth' in servlet with name 'grailsDispatcherServlet'] with root cause
javax.servlet.ServletException: Could not resolve view with name '/login/auth' in servlet with name 'grailsDispatcherServlet'

This is because, now the application is secured and as the security plugin added a chain of filters, the request is redirected to a login page. we do not have a login page yet. We do not need one anyway as this is a RESTful application.

Step 4 Add roles and users

As outlined in the security plugin documentation in Bootstrap.groovy, add the following code:
import com.giri.security.AppUser import com.giri.security.AppUserRole import com.giri.security.Role class BootStrap { def init = { servletContext -> def init = { def adminRole = new Role('ROLE_ADMIN').save() def userRole = new Role('ROLE_USER').save() def testUser = new AppUser('me', 'password').save() def adminUser = new AppUser('admin', 'admin').save() AppUserRole.create testUser, userRole AppUserRole.create adminUser, adminRole AppUserRole.withSession { it.flush() it.clear() } assert AppUser.count() == 2 assert Role.count() == 2 assert AppUserRole.count() == 2 } } def destroy = { } }

Tip
You can even make the above code to create roles and users better with GORM's findOrSaveByXxxx() methods.

def adminRole = Role.findOrSaveByAuthority('ROLE_ADMIN') def userRole = Role.findOrSaveByAuthority('ROLE_USER') def testUser = AppUser.findOrSaveByUsernameAndPassword('me', 'password') def adminUser = AppUser.findOrSaveByUsernameAndPassword('admin', 'admin')

Step 5 Start the application

The application results with the following exception:
ERROR org.springframework.boot.SpringApplication - Application startup failed groovy.lang.GroovyRuntimeException: Could not find matching constructor for: com.giri.security.Role(java.lang.String)

Fix
Add the following missing constructors for AppUser.groovy and Role.groovy
AppUser(String username, String password) { this() this.username = username this.password = password } Role(String authority) { this() this.authority = authority }

Step 6 Add REST Security API plugin with GORM support

Add REST API Security plugin dependency to build.gradle file
ext{ springSecurityRestVersion = '2.0.0.M2' } dependencies { //grails REST API Security plugin compile "org.grails.plugins:spring-security-rest:${springSecurityRestVersion}" compile "org.grails.plugins:spring-security-rest-gorm:${springSecurityRestVersion}" }

Step 7  Restart the application

$ grails run-app Configuring Spring Security Core ... ... finished configuring Spring Security Core Configuring Spring Security REST 2.0.0.M2... ... finished configuring Spring Security REST ... with GORM support Grails application running at http://localhost:8080 in environment: development
Notice the highlighted.

Step 8 Add plugin chainMap property in application.groovy file as described in the documentation

Add the following as outlined in the plugin documentation:
grails.plugin.springsecurity.filterChain.chainMap = [ //Stateless chain [ pattern: '/api/**', filters: 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter'], //Traditional chain //[ pattern: '/**', filters: 'JOINED_FILTERS,-restTokenValidationFilter,-restExceptionTranslationFilter'] ]

Step 9 Generate domain class AuthenticationToken as described in the plugin documentation


$ grails create-domain-class com.giri.security.AuthenticationToken | Created grails-app/domain/com/giri/security/AuthenticationToken.groovy | Created src/test/groovy/com/giri/security/AuthenticationTokenSpec.groovy

Step 10 Update AuthenticationToken.groovy

Open file AuthenticationToken.groovy and update as shown below:
class AuthenticationToken { String tokenValue String username static mapping = { version false } static constraints = { } }

Step 11 Open application.groovy and add the following plugin related properties:

grails.plugin.springsecurity.rest.token.storage.useGorm = true grails.plugin.springsecurity.rest.token.storage.gorm.tokenDomainClassName = 'com.giri.security.AuthenticationToken'

Step 12 Restart the application and point your browser at: http://localhost:8080

Now, you will see the default JSON response:


Step 13 Check dbconsole

Grails comes with database console for the in-memory database H2. By default when the application is run, it runs in development environment and H2 in-memory database is used.

Point your browser at http://localhost:8080/dbconsole to see the in-memory H2 database console. Select Generic H2 Embedded and click connect.  Notice that you will have 4 tables: APP_USER, ROLE, APP_USER_ROLE and AUTHENTICATION_TOKEN
Just click connect and notice all the domain classes added now have corresponding tables created:

Step 14 Test it

The Grails Security REST API plugin provides three end-points: /api/login for login, /api/logout for logout and /api/validate for validating the token.

Let's test it little bit. Since it is a RESTful application, we do not have regular gsp pages/views. We need to send RESTful requests to test our application.

Let's use Curl - a simple command line tool that comes with Mac and is also available for Windows.

Login - Send a POST request with right user credentials

Run the following command to test end-point /api/login. Highlighted is the response received.
$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"username":"me","password":"password"}' http://localhost:8080/api/login HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Cache-Control: no-store Pragma: no-cache Content-Type: application/json;charset=UTF-8 Content-Length: 112 Date: Sun, 19 Jun 2016 15:12:48 GMT {"username":"me","roles":["ROLE_USER"],"token_type":"Bearer","access_token":"664dkbafcuo4prsd02vocvlfvaok5nvl"}

Login - Send a POST request with wrong user credentials

Run the following command to test end-point /api/login. Highlighted is the response received.

$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"username":"me","password":"passwordWrong"}' http://localhost:8080/api/login HTTP/1.1 401 Unauthorized Server: Apache-Coyote/1.1 WWW-Authenticate: Bearer Content-Length: 0 Date: Sun, 19 Jun 2016 15:19:02 GMT

Notice the Unauthorized response

The plugin also has an endpoint to validate the token received upon successful login.

Validate token

Run the following command to test end-point /api/validate. Highlighted is the response received.

$ curl -i -H "Authorization: Bearer 664dkbafcuo4prsd02vocvlfvaok5nvl" http://localhost:8080/api/validate HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Cache-Control: no-store Pragma: no-cache Content-Type: application/json;charset=UTF-8 Content-Length: 112 Date: Tue, 21 Jun 2016 01:03:05 GMT {"username":"me","roles":["ROLE_USER"],"token_type":"Bearer","access_token":"664dkbafcuo4prsd02vocvlfvaok5nvl"}

Highlighted is the response.

NOTE
"Authorization: Bearer <token>" is the default header to be passed.
Set the following in application.grovy to change the header.
grails.plugin.springsecurity.rest.token.validation.useBearerToken = false grails.plugin.springsecurity.rest.token.validation.headerName = 'X-Auth-Token'

If you change the header name as above, your request to login should be like:
$ curl -i -H "X-Auth-Token: 664dkbafcuo4prsd02vocvlfvaok5nvl" http://localhost:8080/api/validate HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Cache-Control: no-store Pragma: no-cache Content-Type: application/json;charset=UTF-8 Content-Length: 90 Date: Tue, 21 Jun 2016 01:20:36 GMT {"username":"me","roles":["ROLE_USER"],"access_token":"664dkbafcuo4prsd02vocvlfvaok5nvl"}

Highlighted is the response

Validate wrong token Run the following. Highlighted is the response


$ curl -i -H "X-Auth-Token: wrong-token-anyway" http://localhost:8080/api/validate HTTP/1.1 401 Unauthorized Server: Apache-Coyote/1.1 Content-Length: 0 Date: Tue, 21 Jun 2016 01:23:38 GMT

Logout Let's test the logout end-point: /api/logout. Run the following and highlighted is the response.


$ curl -i -H "X-Auth-Token: 664dkbafcuo4prsd02vocvlfvaok5nvl" http://localhost:8080/api/logout HTTP/1.1 403 Forbidden Server: Apache-Coyote/1.1 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 21 Jun 2016 01:26:40 GMT {"timestamp":1466472400548,"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/logout"}

Surprisingly we received 403 Forbidden.

Gotcha
For logout to work, add the following pattern to the list of static rules in application.groovy
grails.plugin.springsecurity.controllerAnnotations.staticRules = [ //omitted the rest for brevity [pattern: '/api/logout', access: ['isAuthenticated()'] ]

By adding the above pattern to staticRules, we are specifying to allow the end-point api/logout only if the user is authenticated. Now try to logout.

curl -i -H "X-Auth-Token: 664dkbafcuo4prsd02vocvlfvaok5nvl" http://localhost:8080/api/logout HTTP/1.1 405 Method Not Allowed Server: Apache-Coyote/1.1 X-Application-Context: application:development Content-Length: 0 Date: Tue, 21 Jun 2016 01:34:48 GMT

Highlighted is the response. This time it's 405 Method not allowed.

The reason: We used a GET request to logout. The security plugin has a property grails.plugin.springsecurity.logout.postOnly which is set to true by default. So, it only allows POST request to logout.

Try the following now

$ curl -i -H "X-Auth-Token:664dkbafcuo4prsd02vocvlfvaok5nvl" -X POST http://localhost:8080/api/logout HTTP/1.1 200 OK Server: Apache-Coyote/1.1 X-Application-Context: application:development Content-Length: 0 Date: Tue, 21 Jun 2016 01:36:29 GMT

Highlighted is the response. Now it successfully logged me out.

Now double check by running the validate end-point
$ curl -i -H "X-Auth-Token:664dkbafcuo4prsd02vocvlfvaok5nvl" http://localhost:8080/api/validate HTTP/1.1 401 Unauthorized Server: Apache-Coyote/1.1 Content-Length: 0 Date: Tue, 21 Jun 2016 01:49:02 GMT

Highlighted is the response. Notice that the token that was valid now resulted with 401 Unauthorized.

TIP
To make the logout work with GET request, set grails.plugin.springsecurity.logout.postOnly = false in application.groovy. The GET request to logout should work.

TIP
If you are not comfortable with the command line tool like curl for testing, thee are several graphical tools available. I use this chrome plugin called DTC REST Client from Restlet which is really nice, easy to use and also all API tests can be organized, saved and shared with your team.

Step 15 Lockdown Spring Boot Actuator end-points

Grails 3 underpins Spring Boot. Spring Boot has a module called Actuator which provides various end-points for monitoring and managing a running application. In Part-1 side note, I mentioned about it. These end-points are available at root the application context path.

The stateless chainMap pattern that we added to secure all our /api/** end-points won't secure Actuator end points. In reality, it makes sense to secure Actuator provided application monitoring and management  end-points. There could be many ways to secure these end-points. Here is what I tried which worked.
  • Customize the Actuator end-points path by adding the following spring property in application.yml
  • management: context-path: /api/management
  • With the above spring property we changed end-points path from application root context to '/api/manamgement'. With this change for example the earlier http://localhost:8080/info will be available at http://localhost:8080/api/management/info. Now, Add the following static rule in application.groovy to secure all Actuator end-points.
  • grails.plugin.springsecurity.controllerAnnotations.staticRules = [ ... [pattern: '/api/management/**', access:['isAuthenticated()']] ]
With the above settings, all Actuator end-points are available to logged in users. You can further restrict the access by limiting it to just administrators by adding the role ROLE_ADMIN to the statesRules like:
[pattern: '/api/management/**', access:['ROLE_ADMIN']]

Now we have successfully added security to our app in RESTful way with the two Grails plugins. In the next post, we will add PostgreSQL support to the application.

References

Documentations
Grails Spring Core Plugin Documentation
Grails Spring Security REST Plugin Documentation
Spring Security Documentation
Grails Goodness: Adding Health Check Indicators

Code/APIs
Grails Spring Security Core Plugin code
Grails Spring Security REST Plugin code
Spring Security Code
Spring Security API

4 comments:

Anonymous said...

Excellent! Thanks so much.

Giridhar Pottepalem said...

Thank you!

Anonymous said...

On Step 12 & 13, I am getting a [Could not resolve view with name '/login/auth'] error when trying to load the http://localhost:8080 from the browser. But I am able to run Step 14 without an issue.

Thank you very much for sharing!

nbkhope said...

You got the error about not finding a matching constructor for Role because you did not call it properly!!!

Instead of
def adminRole = new Role('ROLE_ADMIN').save()

You should have used
def adminRole = new Role(authority: 'ROLE_ADMIN').save()

The argument has to be named!

The same goes for the problem with the "AppUser". Make sure to name your arguments:

...User(username: 'me', password: 'password')

Heads up everybody :)