Integrating Grails 2 with Spring Security OpenID(Google) Single Sign-On

Here’s a tutorial on how to add Open ID Authentication on top of Spring Security Core. Basically, you can access your app with your Open ID account which is provided by several websites. Providers include Google,Yahoo!, PayPal, BBC, AOL, LiveJournal, MySpace, IBM, Steam,Sherdog, Orange and VeriSign. For this example, we’ll use Google as our provider.

Steps:

1. Setup

Install Spring Security Core Plugin

grails install-plugin spring-security-core

   And run s2-quickstart to generate initial domain classes needed for Spring Security.

grails s2-quickstart org.myapp User Role

Install Spring Security Open ID Plugin

grails install-plugin spring-security-openid

Note: If  you experience the following error,

 | Error Failed to resolve dependencies (Set log level to ‘warn’ in BuildConfig.groovy for more information):
  – com.google.code.guice:guice:2.0

Add this to BuildConfig.groovy under repositories.

mavenRepo "https://repository.jboss.org/nexus/content/repositories/thirdparty-uploads/"

Run s2-init-openid  for controller and view generation of Open ID

grails s2-init-openid

Run s2-create-openid <Package>.OpenID  (OpenID domain generation)

grails s2-create-openid orgmyapp.OpenID

2. Add URL Mappings

We’ll use OpenIdController.auth action instead of the core plugin’s LoginController.auth since this one supports both OpenID and regular username/password logins. Add these mappings in grails-app/conf/UrlMappings.groovy to support these changes.

      "/login/auth" {
         controller = 'openId'
         action = 'auth'
      }
      "/login/openIdCreateAccount" {
         controller = 'openId'
         action = 'createAccount'
      }
3. Update User domain

Update the generated User domain class and add a hasMany for an openIds property

String username
	String password
	boolean enabled
	boolean accountExpired
	boolean accountLocked
	boolean passwordExpired

    static hasMany = [openIds: OpenID]
4.  Point Log In to Google Open ID

Edit /openid/auth.gsp and insert the highlighted code. Adding this tells the plugin to use Google as Open ID provider.

     <tr>
            <td colspan='2' class="openid-submit" align="center">
                <input type="submit" value="Log in" />
                <input type="hidden"
                       value="https://www.google.com/accounts/o8/id"
                       name="${openidIdentifier}" class="openid-identifier" />
            </td>
     </tr>

5. Update OpenIdController

Update OpenIdController and replace the createAccount and createNewAccount action with the following respectively: OpenIdController.createAccount

def createAccount = {
	String openId = session[OIAFH.LAST_OPENID_USERNAME]

        if (!createNewAccount(openId)) {
			log.debug('create new account failed')
			flash.error = "Invalid email account!"
			redirect action: 'auth', params: params
			//return [command: command, openId: openId]
		}
		else{
			def email = session[OIAFH.LAST_OPENID_ATTRIBUTES].find {
					it.name == 'email'
			}.values[0].toString()

			def username = email.substring(0, email.indexOf("@"));

			authenticateAndRedirect username
		}
	}

OpenIdController.createNewAccount


private boolean createNewAccount(String openId) {
                boolean created = User.withTransaction { status ->
		def firstName, lastName, email
		def config = SpringSecurityUtils.securityConfig
		def openIdAttributes = session[OIAFH.LAST_OPENID_ATTRIBUTES]
			openIdAttributes.each {
				if(it.name.equals('email')){
					email = it?.values[0]
				}
				if(it.name.equals('firstname')){
					firstName = it?.values[0].split(' ').collect{ it.capitalize() }.join(' ')
				}
				if(it.name.equals('lastname')){
					lastName = it?.values[0].split(' ').collect{ it.capitalize() }.join(' ')
				}
			}

			def password = springSecurityService.encodePassword("changethis")
			def username = email.substring(0, email.indexOf("@"))

           def user = User.findByUsername(username)
           if(user == null) {
                user =  new User(username: username, password: password, enabled: true, accountExpired:false,      accountLocked: false, passwordExpired: false)
                user.addToOpenIds(url: openId)
                if (!user.save(flush:true)) {
                   return false
                }
                def openIdRoleNames = config.openid.registration.roleNames
                for(openIdRole in openIdRoleNames) {
                       Role auth = Role.findByAuthority(openIdRole) ?: new Role(authority: openIdRole).save(flush:true,   failOnError: true)
                       new UserRole(user: user, role: auth).save(flush:true, failOnError:true)
                }
                   return true
           } else {
               return false
           }

	}
	return created
}

What does it do?

After granting the access of the Google Account, It  checks the username(email) if it is already registered in the application. If  so, it  will just skip the account creation,  else it will create a new one with the details pulled from the Google account.
So how did we get those information? Open ID enables transport of personal identity information. We get these by adding this in Config.groovy:
grails.plugins.springsecurity.openid.registration.requiredAttributes = [email: 'http://axschema.org/contact/email', firstname: 'http://openid.net/schema/namePerson/first', lastname: 'http://openid.net/schema/namePerson/last']

 Additional Notes:

1. Request Attributes
Request attributes are saved in session[OIAFH.LAST_OPENID_ATTRIBUTES] after successful Open ID authentication.

OpenIdController.createNewAccount


private boolean createNewAccount(String openId) {
        //...
	def openIdAttributes = session[OIAFH.LAST_OPENID_ATTRIBUTES]
		openIdAttributes.each {
			if(it.name.equals('email')){
				email = it?.values[0]
			}
			if(it.name.equals('firstname')){
				firstName = it?.values[0].split(' ').collect{ it.capitalize() }.join(' ')
			}
			if(it.name.equals('lastname')){
				lastName = it?.values[0].split(' ').collect{ it.capitalize() }.join(' ')
			}
		}
	//...
}

You can add more attributes if you need some more information. The list of Open ID Attribute properties can be found here.

2. Role names

By default, roleNames is set to [‘ROLE_USER’]. That means for every user created after Open ID authentication, it is assigned to ROLE_USER role.

OpenIdController.createNewAccount


private boolean createNewAccount(String openId) {
                //...
                  for(openIdRole in openIdRoleNames) {
                       Role auth = Role.findByAuthority(openIdRole) ?: new Role(authority: openIdRole).save(flush:true, failOnError: true)
                       new UserRole(user: user, role: auth).save(flush:true, failOnError:true)
                  }
              //...
}

Leave a comment