Single Sign On Authentication

Note this lecture assumes that the reader has completed the programming assignment using Grails Spring Security and read the Web Framework lecture.

Single Sign On (SSO) is an authentication technique that enables users to authenticate in one place for any number of services and applications provided by an enterprise or organization. Naturally, a server is required to respond to the services and applications requesting authentication. The server needs to identify the user, and find the user details and determine the user authentication status. Finally, the server sends the appropriate response to the services and applications.  MTU uses CAS for it SSO system, 

https://apereo.github.io/cas/6.5.x/index.html,

 and Grails has a Spring Security CAS plugin, 

https://plugins.grails.org/plugin/grails/spring-security-cas

to interface with CAS. In CAS terminology, the server is called the “server”. The CAS server is a Java Enterprise application deployed to a Tomcat server. The service and applications requesting authentication from the server are called the “service”. The CAS server is very slim. Services’ programs do not interface directly with the CAS server, they use a CAS “client” specific to the programming language, Java, .NET, PHP or Apache (stand http) that ensures the proper protocol between the client and server. In addition, the CAS server does not store the users and user details; it relies on a separate datastore for user details, for example a SQL database server or a LDAP server.

https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol

Spring Security and CAS Plugins

The Grails Spring Security CAS Plugin uses the Grails Spring Security Core Plugin.

https://grails.github.io/grails-spring-security-core/5.0.0-RC1/index.html

The Grails Spring Security Core Plugin is an interface for a Grails app to Spring Boot’s Spring Security.

https://docs.spring.io/spring-security/site/docs/5.3.9.RELEASE/reference/html5/

So to understand how to use Grails Spring Security CAS Plugin, we need to review Spring Security for both Grails and Spring Boot.  

Spring Security Architecture

The Spring Security architecture is based on Java Servlet architecture,

https://docs.spring.io/spring-security/site/docs/5.3.9.RELEASE/reference/html5/#servlet-architecture

which uses a “FilterChian” between the client (browser) and Servlet (Java EE web app controller). The individual “Filters” in the FilterChain perform specific actions on and tasks for the client’s request on its way to Servlet. 

  Client
     |
     +
FilterChain       
     |
   Fiter0
     |
   Fiter1
     |
   Fiter2
     |
   Servlet

Spring Security uses a “DelegatingFilterProxy/FilterChainProxy” to inject a SecurityFilterChain. Spring Security does this for two reasons. Most importantly, the FilterChainProxy allows Filters in the SecurityFilterChain to be “beans”, Spring’s component for “dependency injection”. Also the FilterChainProxy enables adding a sub sequence of Filters into the main FilterChain.

  Client
     |
     +
FilterChain       
     |
   Fiter0
     |
   DelegatingFilterProxy/FilterChainProxy/BeanFilter 
     |
     +------------------------------------+ 
                                    SecurityFilterChain                        
                                          |
                                    Security Filter0
                                          |
                                    Security Filter1
                                          |
                                    Security Filter2
     +------------------------------------+
     |
   Fiter2
     |
   Servlet

Spring Security provides a vast array of security filters to perform specific tasks such as authentication and authorization.

https://docs.spring.io/spring-security/site/docs/5.3.9.RELEASE/reference/html5/#servlet-security-filters

Authentication Providers

Many of these filters are “AuthenticationProvider”. They are filters that implement authentication. In particular, the CasAuthenticationFilter implements authentication with a CAS server. Spring Security allows developers to configure these security filters for specific URL patterns. For example, different URLs can use different AuthenticationProviders for authentication.

The Grails Spring Security Plugin integrates Spring’s Spring Security Plugin into the Grails app. Out of the box Grails Spring Security Plugin provides three AuthenticationProvider

  • daoAuthenticationProvider
  • anonymousAuthenticationProvider
  • rememberMeAuthenticationProvider

https://grails.github.io/grails-spring-security-core/5.0.0-RC1/index.html#authenticationProviders

The daoAuthenticationProvider (dao = Data Access Object) is the authentication that you learned in the programming assignment using the Users/Roles tables. The rememberMeAuthenticationProvider is used to authenticate after the user clicks the “Remember Me” checkbox. It probably makes use of cookies. The anonymousAuthenticationProvider is the AuthenticationProvider for a user that is not authenticated.

https://docs.spring.io/spring-security/site/docs/5.3.9.RELEASE/reference/html5/#anonymous

The daoAuthenticationProvider uses an UserDetailsService to provide UserDetails.

https://grails.github.io/grails-spring-security-core/5.0.0-RC1/index.html#userDetailsService

https://docs.spring.io/spring-security/site/docs/5.3.9.RELEASE/reference/html5/#servlet-authentication-userdetails

The UserDetailsService and GrailsUserDetailsService is an interface with a single method, loadUserByUsername.

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/UserDetailsService.html

Note that it throws UsernameNotFoundException, if the username is not found in the User table, and consequently the authentication fails. 

The UserDetails and GrailsUserDetails is another interface that provides convenient methods for retrieving the details of the users, so the web application does not have to make a database call to the User instance. 

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/UserDetails.html

Grails Spring Security Chain Maps

Grails Spring Security has several techniques for configuring the Security FilterChains.

https://grails.github.io/grails-spring-security-core/5.0.0-RC1/index.html#filters

The most appropriate technique is to apply different Security FilterChains for different URL patterns in the chainMap for the filterChain.

https://grails.github.io/grails-spring-security-core/5.0.0-RC1/index.html#chainmap

The chainMaps maps URL patterns with an array of filters. 

Grails Spring Security CAS Plugin

The Grails Spring Security CAS plugin adds the Spring Security CasAuthenticationFilter to the Grails Spring Security Core plugin.

https://docs.spring.io/spring-security/site/docs/5.3.9.RELEASE/reference/html5/#servlet-cas

https://grails.github.io/grails-spring-security-cas/snapshot/index.html

Note that adding the Grails Spring Security CAS plugin to your build dependency will automatically add the Grails Spring Security Core. This is convenient, but more important it assures that the appropriate version of Grails Spring Security Core is used with the CAS plugin. 

CAS Configuration

The Grails Spring Security CAS plugin makes it easy to configure CAS authentication.

https://grails.github.io/grails-spring-security-cas/snapshot/index.html#configuration

The minimum configuration is to specify 

  • cas.serverUrlPrefix
  • cas.loginUri
  • cas.serviceUrl

The serverUrlPrefix is the protocol and domain for the CAS server plus (typically) “/cas”. For example:

https://sso.mtu.edu/cas

The loginUri is the CAS action for login, typically “/login”. The serviceUrl is the full URL for the service (web application) CAS login endpoint. Typcially this is 

https://<Domain>:<Port>/<Server Context Path>/login/cas

CAS Authentication Filter and Patching

The CasAuthenticationFilter is added in sequence before the DaoAuthenticationProvider. This implies that the user needs to be authenticated by both providers, CAS and DAO. This configuration has advantages and disadvantages. The advantage is that the DaoAuthenticationProvider can use the User/Roles tables for authorization of specific endpoints of your web application. The disadvantage is that your web application needs to patch the Grails Spring Security. There are two techniques for patching Grails Spring Security Core.

Perhaps the easiest technique is to use the chainMap to remove the daoAuthenticationProvider from the URL patterns that should not use DAO authentication. For example in application.groovy

grails.plugin.springsecurity.filterChain.chainMap = [
   [pattern: '/nodoa/**', filters: 'JOINED_FILTERS,-daoAuthenticationProvider'],
   [pattern: '/**',       filters: 'JOINED_FILTERS']
]

This will remove DAO authentication for endpoints of “nodoa”, and DAO authentication will remain for the rest of the web application endpoints. But this has the disadvantage that the web application can use roles to authorize users for specific endpoints. Consequently, we’ll not pursue this technique. 

The preferred technique to patch Grails Spring Security is to implement a custom GrailsUserDetailsService. This will enable using CAS for authentication and DAO for granting authority to users and using the authorization rules. The implementation avoids throwing the UsernameNotFoundException by making a GrailsUser (the UserDetails for Grails) with a special NO_ROLES role  if the username is not found in the User table. If the username name is found then the users acquirers the roles for the user from the userrole table. So we have it both ways. The GrailsDetailService will need to be registered as a bean in grails-app/conf/spring/resources.groovy so the DAO knows to use it.

There are several ways that users can be assigned roles, but many applications will associate roles with a user in BootStrap.groovy.

The “testcas” example below will show the details of patching Grails Spring Security by implementing a custom  GrailsUserDetailsService. 

Implement testcas

This section goes through installing and using the Grails Spring Security CAS plugin step-by-step. The example project, testcas, repository is at 

https://github.com/2023-UI-SP/testcas

Currently, the repository is private. Let me know if you wish to clone the repository. 

Installation

Install the plugin by including it in the “dependencies” section of “build.gradle”

dependencies {
  …
    // For CAS login. spring-security-cas provides spring-security-core.
    compile 'org.grails.plugins:spring-security-cas:3.1.0'
}

Do not install the Spring Security Core plugin. The CAS plugin will automatically install the correct version of Spring Security Core.

The example project, testcas, uses Grails version 3.3.14 and Java azul 1.8.0_312″ and Spring Security CAS version 3.1.0. If you are using different versions of Grails or Java, you should immediately test that grails app builds without issues immediately. 

Local CAS Server

The example project comes with two zipped files in cas-local-server. 

  • server.tar.gz
  • cas-server-webapp-3.4.2.-no-ssl.war

The server.tar.gz is a trimmed-down Tomcat 8.0.26 which will run on port 9090. The cas-server-webapp-3.4.2-no-ssl.war Is a CAS server WAR that allows non-ssl access to the server. It also authenticates any user with matching username and password. 

To test your web app with the local CAS Server, you unpack server.tar.gz in the cas-local-server. This will create a server/ folder with a Tomcat server. Also in the server/ folder is a bin/ folder with two scripts:

  • startup.sh
  • shutdown.sh

Start the tomcat server by running the startup.sh script. Then deploy the cas-seve-webapp-3.4.2-no-ssl.war into the server/webapp/ folder. Tomcat should explode the war. 

You can now run your grails and test CAS authentication locally. To clear login to the CAS server, you can shutdown the Tomcat server using shutdown.sh script and start the Tomcat server again. 

You can also get the local Tomcat and CAS server at Grails Spring Security CAS project code on GitHub:

https://github.com/grails/grails-spring-security-cas/tree/3.1.x/testapps

Configure CAS Service 

In your grails application, you configure the CAS plugin in grails-app/conf/application.yml. To configure for the CAS server on localhost, use 

---
grails:
    plugin:
        springsecurity:
            cas:
                # Configuration below is for testing with "mock" CAS server on localhost
                # You need to start the server in the cas-local-server/ directory.
                serverUrlPrefix: http://localhost:9090/cas
                loginUri: /login
                serviceUrl: http://localhost:8080/testcas/login/cas
server:
    contextPath: '/testcas'

Where “testcas” is your web-app server context path. Do not forget the three dashes at the top of the configuration code. 

Later when you deploy your application, you will need to register the web app with MTU IT. 

https://servicedesk.mtu.edu/TDClient/1801/Portal/KB/ArticleDet?ID=65354

and change the CAS configuration to something like

---
grails:
    plugin:
        springsecurity:
            cas:
                # Configuration below is for MTU SSO/CAS server.
                serviceUrl: https://hci-dev.cs.mtu.edu:8143/testcas/login/cas
                serverUrlPrefix: https://sso.mtu.edu/cas
                loginUri: /login
                useSingleSignout: false
server:
    contextPath: '/testcas'

Where “hci-dev.cs.mtu.edu:8143” is the domain and port number for your tomcat server and “testcas” is the server context path for your web application.

User & Roles Domain Classes

Use s2-quickstart script provided by the Grails Spring Security plugin to make the User and Roles domain classes. To create the testcas example app, I used 

grails s2-quickstart testcas User Role

This will create the three domain classes for authenticating users and authorizing URLs with roles. In addition it will create grails/conf/application.groovy for configuring Spring Security. 

Secure Controller and Authorization 

You can make your secured controller in grails-app/controllers/ folder. The testcas example application used SecureController.groovy

package testcas

/**
 * A test controller for testing authorization of users authenticated with a CAS service.
 * See the authorization, static rules, in init/application.groovy.
 */
class SecureController {

    def index() {
        String username = getPrincipal().username
        render "In index: ${username}"
    }

    def any(){
        String username = getPrincipal().username
        render "In any, username: ${username}"
    }

    def users(){
        String username = getPrincipal().username
        render "In users, username: ${username}"
    }

    def admins(){
        String username = getPrincipal().username
        render "In admins, username: ${username}"
    }
}

There is nothing special about this controller. It only provides the end point to set Spring Security rules. In grails-app/conf/application.groovy, configure the URL authorization in the grails.plugin.springsecurity.controllerAnnotations.staticRules property. The configuration for the testcas example project is

grails.plugin.springsecurity.controllerAnnotations.staticRules = [
	...
	[pattern: '/secure/index',   access: ['permitAll']],
	// Below provide authorization for actions of SecureController
	[pattern: '/secure/any',     access: ['IS_AUTHENTICATED_REMEMBERED']], 
	[pattern: '/secure/users',   access: ['ROLE_USER']],
	[pattern: '/secure/admins',  access: ['ROLE_ADMIN']]
]

These settings allow anyone to access to secure/index while the user needs to be authenticated by the CAS server to access the secure/any, secure/users and secure/admins. In addition to access the secure/users and secure/admins the user needs to be granted the ROLE_USER and ROLE_ADMIN authority by Spring Security DAO content provider. 

Register User with DAO Content Provider

Users can be registered with DAO Content Provider and given roles in grails-app/init/BootStrop.groovy. The testcas example application used

package testcas

class BootStrap {

    def init = { servletContext ->
        Role roleAdmin = new Role(authority: 'ROLE_ADMIN').save()
        Role roleUser = new Role(authority:  'ROLE_USER').save()

        /**
         * Boot strapping below is for testing authorization, roles, supplied by the app.
         */
         /*
         /*
          * For testing roles on localhost.
          */
        User user = new User(username: 'user', password: 'a')
        UserRole.create user, roleUser, true

        User admin = new User(username: 'admin', password: 'a')
        UserRole.create user, roleAdmin, true

    }
    def destroy = {
    }
}

Two roles are created ROLE_ADMIN and ROLE_USER and associated with two users, admin and user respectively. Note the passwords are not used, so they don’t matter. 

Note when deploying your application on the Tomcat server using the MTU CAS server, you will need to change the usernames to the MTU usernames. For example, to grant me the admin role. You would use 

package testcas

class BootStrap {

    def init = { servletContext ->
        Role roleAdmin = new Role(authority: 'ROLE_ADMIN').save()
        Role roleUser = new Role(authority:  'ROLE_USER').save()

        /**
         * Bootstrapping below is for testing authorization, roles, supplied by the app.
         */
         /*
         * For testing with apps using MTU CAS server
         * To test with MTU, you Need to change username below to your MTU username, 
         * any password will work.
         * Use https://sso.mtu.edu/cas/logout to logout of MTU SSO
         */
        User pastel = new User(username: 'pastel', password: 'fake').save()
        /*
         * You can choose to give the pastel user any role or even no roles.
         * For no roles, don't use the line below.
         */
        UserRole.create pastel, roleAdmin, true  
    }
    def destroy = {
    }
}

Note the user can be granted any role or no role. If you do not want to grant a  role, do not create the UserRole entry. The user can access any endpoint with the staticRule IS_AUTHENTICATED_REMEMBERED.   

Test Locally

To test your grails app on localhost using a CAS server, you need to start the CAS server by running the Tomcat startup script in a bash terminal in the cas-local-server/ folder:

./server/bin/startup.sh

You can run your grails application. And navigate to a secure page. You will be presented with the CAS server login page. Login with any matching username and same password. At this point you should be able to access secure/index

but you will need to be authenticated as “user” 

  • secure/any
  • secure/user

or as “admin” to access

  • secure/any
  • secure/admin

To test the different users you’ll need to reset the CAS server by stopping and starting the Tomcat server for the CAS server.

Custom UserDetailsService

So that your app does not need to bootstrap users to access secure endpoints, we need to create a custom UserDetailServer that does not throw the UsernameNotFoundException. The custom UserDetailsService is created in src/main/groovy/ folder. The testcas example application made src/main/groovy/MyUserDetailsService.groovy:

/**
 * MyUserDetailsService should be used with a trusted CAS Server. It avoids requiring local authentication of username,
 * meaning that there is an entry for username in the app's User table. See
 * https://stackoverflow.com/questions/35582773/grails-spring-security-cas-cannot-get-login-to-work-with-non-local-user
 * https://grails.github.io/grails-spring-security-core/3.2.x/index.html#userDetailsService
 */

class MyUserDetailsService implements GrailsUserDetailsService {

    /**
     * Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least
     * one role, so we give a user with no granted roles this one which gets
     * past that restriction but doesn't grant anything.
     */
    static final List NO_ROLES =
                [new SimpleGrantedAuthority(SpringSecurityUtils.NO_ROLE)]


    UserDetails loadUserByUsername(String username, boolean loadRoles)
            throws UsernameNotFoundException {
        return loadUserByUsername(username)
    }

    @Transactional(readOnly=true, noRollbackFor=[IllegalArgumentException, UsernameNotFoundException])
    UserDetails loadUserByUsername(String username)
    {
//        println "In MyUserDetailsService, loadUserByUsernane"
        def user = User.findByUsername(username)

        // No local user in my db: create one from this username
        if (!user) {
//            println "In MyUserDetailsService, no user"
            return new GrailsUser(username, '', true, true, true, true, NO_ROLES, 999)
        }

        def authorities = user.authorities.collect {
            new SimpleGrantedAuthority(it.authority)
        }

        return new GrailsUser(user.username, user.password,
                user.enabled, !user.accountExpired, !user.passwordExpired,
                !user.accountLocked, authorities ?: NO_ROLES, user.id)
    }
}

The code creates a NO_ROLES array for granted authority with only NO_ROLE authority. It then looks up the user in the User table. If the user is not found it creates the GrailsUser with the NO_ROLES authority and returns it  instead of throwing the exception. If the user is found in the User table it makes an authority table and creates and returns the GrailsUser.  

Note, reviewing the code during the writing of this lecture, I believed that the code can be improved. The GrailsUser created for the user not found in the User table is created with id 999. This might cause a problem if there exists an entry with id 999 in the User table. I think that a different id might be better, perhaps 0 or -1.

Finally the custom UserDetailsService must be registered as a Spring Bean so that DAO uses it. This is done in grails-app/conf/spring/resources.groovy. If the file does not exist then create it. The testcas example app used 

import testcas.MyUserDetailsService
// Place your Spring DSL code here
beans = {
    ...
    // The bean registered below avoids local authentication
    userDetailsService(MyUserDetailsService)
}

This is using Spring Bean DSL

https://docs.grails.org/5.2.5/guide/single.html#spring

Test Locally Again

To test your custom UserDetailsService, start the CAS Tomcat server, and run your app. Now any authenticated user by the CAS server with the same username and password should have access to any endpoint with the IS_AUTHENTICATED_REMEMBERED static rule and does have to be bootstrapped in the User Table or granted any specific role. 

Do not forget to stop the CAS Tomcat server when you are done testing.

Register App with MTU CAS Server

To register your app with the MTU CAS server, fill out the form at

https://servicedesk.mtu.edu/TDClient/1801/Portal/Requests/ServiceDet?ID=34627

You will need to provide your Tomcat server domain and port number. Fulfilling the request to register your app will take a few days.

Test Using MTU CAS Server

After a few days, you should get an email saying that your application is registered. To test on the MTU CAS server you will need to change the configuration in 

  • grails-app/init/BootStrap.groovy
  • grails-app/conf/Application.yml 

Then deploy your application to your Tomcat server. You will need to use the MTU username but the passwords are not important in BootStrap.groovy. 

During your testing, you’ll probably want to logout MTU CAS server. Do this at 

https://mymichigantech.mtu.edu/

and click the Sign Out button.

Review 

Although from the first reading of this lecture it seems like a lot of coding is required to implement Single Sign On, it is not really that much. Below is the complete list of tasks.

  1. Install the Spring Security CAS plugin as a dependency in build.gradle. Test that the app still builds.
  2. Download and unpackage the CAS Tomcat server for local testing.
  3. Configure the CAS Service in grails-app/conf/application.yml.
  4. Use s2-quickstart to make the User and Role domains.
  5. Make your controllers and secure them in grails-app/conf/application.groovy Bootstrap the users in grails-app/init/BootStrap.groovy
  6. Test locally
  7. Make the custom UserDetailsService in src/main/groovy and register it in grails-app/conf/spring/resources.groovy.
  8. Test locally again.
  9. Register your app with MTU CAS server
  10. Test using MTU CAS server by reconfiguring app and deploying

References

List of resources I used to write this lecture. 

GitHub Example Project

https://github.com/2023-UI-SP/testcas

CAS plugin configuration

https://github.com/2023-UI-SP/testcas/blob/main/grails-app/conf/application.yml

BootStrap.groovy

https://github.com/2023-UI-SP/testcas/blob/main/grails-app/init/testcas/BootStrap.groovy

Mock/Localhost CAS Server

https://github.com/2023-UI-SP/testcas/tree/main/cas-local-server

Custom UserDetailService

https://github.com/2023-UI-SP/testcas/blob/main/src/main/groovy/testcas/MyUserDetailService.groovy

Registering the Custom UserDetailsService as the bean

https://github.com/2023-UI-SP/testcas/blob/main/grails-app/conf/spring/resources.groovy

MTU CAS Knowledge Base Articles

CAS client configure

https://servicedesk.mtu.edu/TDClient/1801/Portal/KB/ArticleDet?ID=65355

 information on registering a CAS service

https://servicedesk.mtu.edu/TDClient/1801/Portal/KB/ArticleDet?ID=65354

CAS Registration Form

https://servicedesk.mtu.edu/TDClient/1801/Portal/Requests/ServiceDet?ID=34627

CAS Documentation

https://apereo.github.io/cas/6.5.x/index.html

CAS CLient documentation

https://apereo.github.io/cas/6.5.x/integration/CAS-Clients.html

CAS Spring Security Client Example

https://apereo.github.io/cas/6.5.x/integration/CAS-Clients.html

Grails Spring Security CAS plugin

https://plugins.grails.org/plugin/grails/spring-security-cas

Documentation

https://grails.github.io/grails-spring-security-cas/

https://grails.github.io/grails-spring-security-cas/snapshot/index.html

GitHub 

https://github.com/grails/grails-spring-security-cas

The Grails Spring Security CAS plugin has “test apps”

https://github.com/grails/grails-spring-security-cas/tree/4.0.x/testapps

Stackoverflow – Grails Spring Security CAS without local authentication

https://stackoverflow.com/questions/35582773/grails-spring-security-cas-cannot-get-login-to-work-with-non-local-user

Grails Spring Security Documentation

https://grails.github.io/grails-spring-security-core/3.2.x/index.html

s2-quickstart script

https://grails.github.io/grails-spring-security-core/3.2.x/index.html#s2-quickstart

Request Mapping/static rules

https://grails.github.io/grails-spring-security-core/3.2.x/index.html#requestMappings

Authentication

https://grails.github.io/grails-spring-security-core/3.2.x/index.html#authentication

Custom UserDetailsService

https://grails.github.io/grails-spring-security-core/3.2.x/index.html#userDetailsService

Controller Methods

https://grails.github.io/grails-spring-security-core/3.2.x/index.html#controllerMethods

Helper Classes

https://grails.github.io/grails-spring-security-core/3.2.x/index.html#helperClasses

Spring Security Documentation

Authorization

https://docs.spring.io/spring-security/reference/servlet/authorization/index.html

CAS Authentication

https://docs.spring.io/spring-security/reference/servlet/authentication/cas.html#servlet-cas

Stackoverflow 

Get/show username

https://stackoverflow.com/questions/17678599/how-to-display-cas-authentication-username-in-grails-application

https://stackoverflow.com/questions/775053/grails-and-spring-security-how-do-i-get-the-authenticated-user-from-within-a-co

https://github.com/grails/grails-spring-security-cas/issues/12

Grails Spring Security CAS without local authentication

https://stackoverflow.com/questions/35582773/grails-spring-security-cas-cannot-get-login-to-work-with-non-local-user