Grails Spring Security Tutorial
There are many security features in Spring Security such as authentication, authorization, instance-based control, and others. Grails developers use Spring security to secure the application.
Table Of Contents
1. Overview
This is an in-depth article on Grails Spring Security. Grails opensource framework is used for designing and developing full-stack applications. Spring security plugin provides spring security for Grails application. The plugin can be customized for various configuration options. Interfaces are used for extensibility.
2. Grails Spring Security
2.1 Prerequisites
Java 8 is required on the linux, windows or mac operating system. Gradle 5.4.1 version can be used for building gradle projects. Grails 3.3.10 is used for creating Grails Helloworld projects. Apache tomcat 9.0 is used as a servlet container to deploy Grails Helloworld example.
2.2 Download
You can download Java 8 from the Oracle web site . Likewise, Gradle 5.4.1 can be downloaded from this website. Grails binary distribution can be downloaded from github site. Similarly, Apache Tomcat 9.0 can be downloaded from the apache website.
2.3 Setup
2.3.1 Java Setup
You can set the environment variables for JAVA_HOME and PATH. They can be set as shown below.
Java Environment
JAVA_HOME=”/jboss/jdk1.8.0_73″ export JAVA_HOME PATH=$JAVA_HOME/bin:$PATH export PATH
2.3.2 Grails Setup
You can set the Grails home in the PATH as shown below:
Grails Environment
export GRAILS_HOME=/path/to/grails export PATH="$PATH:$GRAILS_HOME/bin"
2.3.3 Gradle Setup
The environment variables for Gradle are set as below:
Gradle Environment
GRADLE_HOME="/opt/gradle/gradle-5.4.1/bin" export GRADLE_HOME=$GRADLE_HOME/bin/ export PATH=$PATH:$GRADLE_HOME
2.4 Running Gradle
You can check the version of the gradle by using the command gradle –-version. The command for running Gradle is as below:
Gradle Version
gradle --version
The output of the executed Gradle command is shown below.
2.5 Running Grails
You can check the version of the Grails by using the command “grails –v”. The command for running Grails is as below:
Grails Version
grails -v
The output of the executed Grails command is shown below.
2.6 Hello World in Grails
Grails framework is used for full-stack application development. It is an opensource framework. It cuts down the challenges in creating web applications using Java. You can create a Grails application by using the command below:
Hello World
grails create-app HelloWorld
The output of the executed Grails command is shown below.
“CreateApp” command creates the HelloWorld folder. The folder contains the Gradle build based project for Grails. Folder structure is shown below:
Controllers are generated by using commands such as create-controller or generate-controller. You can create a controller by using the command below inside the HelloWorld folder:
Create Controller
grails create-controller Hello
A controller has action methods which are public. These methods map to a URI of a page. You can add the code to show “Greetings” inside the generated controller code. The code implementation of the HelloController
Class is shown below:
Hello Controller
package helloworld class HelloController { def index() { render "Greetings" } }
You can run the Grails app in grails console using the command below:
Run App
run-app
The snap shot of the grails console is shown below:
You can access the Grails app in the browser from this URL: http://localhost:8080/ . The page rendered is shown below:
You can select the Hello Controller and click on the link. The following page shows up:
2.7 Testing Grails Application
Grails Framework has features for automated testing. Unit testing and functional testing can be done using the framework. You can modify the HelloWorld/src/test/groovy/helloworld/HelloControllerSpec.Groovy to test the index method. The code implemented for HelloControllerSpec
is shown below:
Unit Test
package helloworld import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class HelloControllerSpec extends Specification implements ControllerUnitTest { def setup() { } def cleanup() { } void "test something"() { when: controller.index() then: response.text == 'Greetings' } }
You can run test the Grails app using the command below:
Test Grails App
grails test-app
The output of the executed grails command is shown below.
2.8 Grails IDE Integration
You can configure the Groovy Eclipse plugin from the distribution site. The screen shot below shows the configuration of Groovy Eclipse plugin from Help-> Install-> New Software.
The groovy version is set from Eclipse’s Preferences -> Groovy ->Compiler. The setting of the groovy version 2.4.16 is shown below:
Spock plugin can be installed with eclipse from this site. The screenshot shows the spock plugin installation.
You need to install SpringSource Tool Suite Grails Support(STS) from the distribution site. You need to also ensure that Buildship gradle Integration plugin is installed. The snapshot below shows the installed gradle version.
2.9 Building with Gradle
You can import the project HelloWorld which was a Gradle project created in section 2.6. The snapshot below shows the import wizard from the Eclipse menu File-> Import.
After the import, Gradle Grails project can be viewed in the eclipse. The screen shot below shows the imported project.
From the Gradle tasks view, You can see all the gradle tasks. To execute the grails app, click on bootRun. The screenshot below shows the gradle tasks view.
The grails app can be accessed at http://localhost:8080 when the gradle runs the Grails app on eclipse. The snapshot of the Grails app and Gradle task execution is shown below.
The HelloController can be accessed and the page renders to show the “Greetings” message. The rendered page is shown below:
2.10 Deploying a Grails App
War file is deployed on the typical servlet containers such as Tomcat, Jetty, etc. war command is used for generating a war file. You can deploy a Grails app on to a container which supports Java Servlet 3.0 specification. The command to create a war file is shown below:
Deploying Grails App
grails war
The output of the executed grails command is shown below.
The war file from build/libs can be deployed on apache tomcat. The startup script of the tomcat is executed. The screen shot shows the execution of the script and the browser rendering the page at http://localhost:8080.
The controller page is accessed by clicking on the link. The page renders as shown below in the screen shot.
2.11 Grails Spring Security
2.11.1 Role Based Security
This section talks about the configuration and extension of spring security plugin for Grails apps. The configuration can be maintained in conf/application.yml. It can also be maintained in conf/application.groovy. application.yml can have the application-specific values and the defualt values can be in the DefaultSecurityConfig.groovy. The application-specific values are overridden with defaults and the settings are merged with the default configuration. The application.yml used is shown below:
application.yml
--- grails: profile: web codegen: defaultPackage: com.app.security spring: transactionManagement: proxies: false gorm: reactor: # Whether to translate GORM events into Reactor events # Disabled by default for performance reasons events: false info: app: name: '@info.app.name@' version: '@info.app.version@' grailsVersion: '@info.app.grailsVersion@' spring: main: banner-mode: "off" groovy: template: check-template-location: false # Spring Actuator Endpoints are Disabled by Default endpoints: enabled: false jmx: enabled: true --- grails: mime: disable: accept: header: userAgents: - Gecko - WebKit - Presto - Trident types: all: '*/*' atom: application/atom+xml css: text/css csv: text/csv form: application/x-www-form-urlencoded html: - text/html - application/xhtml+xml js: text/javascript json: - application/json - text/json multipartForm: multipart/form-data pdf: application/pdf rss: application/rss+xml text: text/plain hal: - application/hal+json - application/hal+xml xml: - text/xml - application/xml urlmapping: cache: maxsize: 1000 controllers: defaultScope: singleton converters: encoding: UTF-8 views: default: codec: html gsp: encoding: UTF-8 htmlcodec: xml codecs: expression: html scriptlets: html taglib: none staticparts: none endpoints: jmx: unique-names: true --- hibernate: cache: queries: false use_second_level_cache: false use_query_cache: false dataSource: pooled: true jmxExport: true driverClassName: org.h2.Driver username: sa password: '' environments: development: dataSource: dbCreate: create-drop url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE test: dataSource: dbCreate: create-drop url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE production: dataSource: dbCreate: none url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE properties: jmxEnabled: true initialSize: 5 maxActive: 50 minIdle: 5 maxIdle: 25 maxWait: 10000 maxAge: 600000 timeBetweenEvictionRunsMillis: 5000 minEvictableIdleTimeMillis: 60000 validationQuery: SELECT 1 validationQueryTimeout: 3 validationInterval: 15000 testOnBorrow: true testWhileIdle: true testOnReturn: false jdbcInterceptors: ConnectionState defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED --- grails: plugin: springsecurity: apf: storeLastUsername: true useSwitchUserFilter: true switchUser: targetUrl: /secure/ adh: errorPage: null # to trigger a 403 userLookup: userDomainClassName: org.app.security.auth.LoginUser authorityJoinClassName: org.app.security.auth.LoginUserRole authority: className: org.app.security.auth.LoginRole controllerAnnotations: staticRules: - pattern: / access: - permitAll - pattern: /dbconsole/* 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 - pattern: /login/impersonate access: - ROLE_SUPERVISOR
resources.groovy is configured for LoginPasswordEncoderListener . Sample resources.groovy is shown below.
resources.groovy
import com.app.security.auth.LoginPasswordEncoderListener beans = { userPasswordEncoderListener(LoginPasswordEncoderListener) }
Authentication can be done in Grails using spring security. The authentication object checks if the present user can execute a secured action. The action can be URL access, domain object securing, method level security and etc., The authentication can be pluggable using spring security. There exists an overlap between login authentication and user representation. LoginUserDataService
will have an implementation of the method of findLoginUserUsername
with parameter
username. LoginUserDataService
class code is shown below.
LoginUserService.groovy
package com.app.security.auth import grails.gorm.services.Service import groovy.transform.CompileStatic import com.app.security.LoginUser @CompileStatic @Service(LoginUser) interface LoginUserDataService { LoginUser save(String username, String password, boolean enabled) List findLoginUserUsername() }
LoginUser
class has properties username, password and enabled. Authorities property is implemented as a method named getAuthorities
. Sample code is shown below:
LoginUser
package com.app.security import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import grails.compiler.GrailsCompileStatic @GrailsCompileStatic @EqualsAndHashCode(includes='username') @ToString(includes='username', includeNames=true, includePackage=false) class LoginUser implements Serializable { private static final long serialVersionUID = 1 String username String password boolean enabled = true boolean accountExpired boolean accountLocked boolean passwordExpired Set getAuthorities() { (LoginUserRole.findAllByUser(this) as List)*.role as Set } static constraints = { password blank: false, password: true username blank: false, unique: true } static mapping = { password column: '`password`' } }
Authority class helps in presenting the user roles in the web app. URL are restricted to the users by assigning access rights. Users can have multiple roles with different access rights in the application. Default user role is a virtual role named ROLE_NO_ROLES. Typically this role does not have any secure resources for this role. A LoginRole
class code sample is shown below.
LoginRole.groovy
package com.app.security import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import grails.compiler.GrailsCompileStatic @GrailsCompileStatic @EqualsAndHashCode(includes='authority') @ToString(includes='authority', includeNames=true, includePackage=false) class LoginRole implements Serializable { private static final long serialVersionUID = 1 String authority static constraints = { authority blank: false, unique: true } static mapping = { cache true } }
A Role class will have authority, constraints, and mapping. The role names can be configured with names starting with “ROLE_”. Person and Authority have many to many relationship to each other. A LoginUserRole
represents the relationship for users having more than one role. The LoginUserRole
code is shown as below.
LoginUserRole.groovy
package com.app.security import grails.gorm.DetachedCriteria import groovy.transform.ToString import org.codehaus.groovy.util.HashCodeHelper import grails.compiler.GrailsCompileStatic @SuppressWarnings(['FactoryMethodName', 'Instanceof']) @GrailsCompileStatic @ToString(cache=true, includeNames=true, includePackage=false) class LoginUserRole implements Serializable { private static final long serialVersionUID = 1 LoginUser user LoginRole role @Override boolean equals(other) { if (other instanceof LoginUserRole) { other.userId == user?.id && other.roleId == role?.id } } @Override int hashCode() { int hashCode = HashCodeHelper.initHash() if (user) { hashCode = HashCodeHelper.updateHash(hashCode, user.id) } if (role) { hashCode = HashCodeHelper.updateHash(hashCode, role.id) } hashCode } static LoginUserRole get(long userId, long roleId) { criteriaFor(userId, roleId).get() } static boolean exists(long userId, long roleId) { criteriaFor(userId, roleId).count() } private static DetachedCriteria criteriaFor(long userId, long roleId) { LoginUserRole.where { user == LoginUser.load(userId) && role == Role.load(roleId) } } static LoginUserRole create(Login user, LoginRole role, boolean flush = false) { def instance = new LoginUserRole(user: user, role: role) instance.save(flush: flush) instance } static boolean remove(Login u, LoginRole r) { if (u != null && r != null) { LoginUserRole.where { user == u && role == r }.deleteAll() } } static int removeAll(Login u) { u == null ? 0 : LoginUserRole.where { user == u }.deleteAll() as int } static int removeAll(LoginRole r) { r == null ? 0 : LoginUserRole.where { role == r }.deleteAll() as int } static constraints = { role validator: { LoginRole r, LoginUserRole ur -> if (ur.user?.id) { LoginUserRole.withNewSession { if (LoginUserRole.exists(ur.user.id, r.id)) { return ['userRole.exists'] } } } } } static mapping = { id composite: ['user', 'role'] version false } }
The command below executes the above code snippets:
Grails Run App
grails run-app
The output of the executed command is shown below.
The screen shot below shows the execution of the sample code and the browser rendering the page at http://localhost:8080.
2.11.2 Security Group
A security group can consist of multiple users. The access rights can be assigned to a group. The application will have multiple groups of users having different levels of access.
2.11.3 Hierarchical Roles
Roles can be hierarchical to minimize the clutter in application request mappings. The configuration options for the hierarchical roles can be role Hierarchy and roleHierarchy entry class name. Sample hierarchy can be as shown below:
Role Hierarchy
grails.plugin.springsecurity.roleHierarchy = ''' ROLE_SUPER_ADMIN > ROLE_FINANCE_ADMIN ROLE_FINANCE_ADMIN > ROLE_OPERATIONS_ADMIN ROLE_OPERATIONS_ADMIN > ROLE_ADMIN '''
3. Download the Source Code
You can download the full source code of this example here: Grails Spring Security Tutorial
Please solve this issue
https://github.com/grails/grails-core/issues/11646?fbclid=IwAR2FibE4ztX7qPBheWoBAhILkNkeRKB_E9Iu3L-3e9rR6Po5_gZWouiEz8Q