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,
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?
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
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) } //... }