Tuesday, November 1, 2011

Single Sign On to Google Apps Marketplace with a Grails application

Hey, have you tried Grails?  If you love Java and love Spring, you are going to adore Groovy on Grails (www.grails.org).  Last week I decided to try out Groovy on Grails using the excellent free ebook located here:  free ebook - yes please.  The book took me two days to navigate through and if you have any experience in Ruby on Rails, you are going to love it.

Grails applications are typically called Groovy on Grails.  Groovy is the language and Grails is the utility that uses convention over configuration to build easily, web applications.  I won't go into too much detail here as the book says it better than I can but it is well worth checking out.

SSO - Google Apps Marketplace

As usual I always try to do something "complicated" in order to test the validity and maturity of any framework.  My usual test case is to try Single Sign On with the Google Apps Marketplace.  Again I am not going to go into too much details on what that actually means but if you are a Google Apps user and you have installed a third party application it is a way of authenticating the user without having to ask them for their credentials.  Since I build apps for the Google Apps Marketplace I have to ensure I can do this otherwise the framework itself won't be a good choice.  In addition because I am not an OpenID expert and prefer to stand on the shoulders of giants, the ease at which this can be implemented and understood is very important to me. 

Since Groovy is a superset and dynamic language and can basically use Java and Java dependencies and since I had already built a SSO solution in a Java-GWT application for www.contactpa.com, I figured this should be fairly simple.  On the whole it was my biggest problem was understanding how Dependency Injection works with Grails / Groovy and how to include dependencies in my project.

All code is available here on this github repository.

What did I learn?

Here are the pitfalls and problems I overcame whilst building this project:

Dependencies:  I love/hate Maven, I have enough power to know how to do something and resolve issues but not a complete mastery, I am a perfectionist, to really understand.  Anyhow I recognise the power of Maven and love it otherwise.  So my first question was how to include dependencies.  Now I tried messing around with the project by mavenizing but this left me with a whole host of other issues which I couldn't understand.  Mainly due to my lack of experience in Grails.  So I decided to use the standard approach.  On conf/BuildConfig.groovy there are several sections to add dependencies.  Anyone with maven experience will understand this coding convention:
    dependencies {
        // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.

        // runtime 'mysql:mysql-connector-java:5.1.13'
  
  runtime 'mysql:mysql-connector-java:5.1.13'
  compile group:'org.openid4java', name:'openid4java-consumer', version:'0.9.6'
  compile group:'com.google', name:'step2-common', version:'1-SNAPSHOT'
  compile group:'com.google', name:'step2-consumer', version:'1-SNAPSHOT'
  compile group:'com.google.inject', name:'guice', version:'3.0'
  compile group:'javax.servlet', name:'jstl', version:'1.2'
  compile group:'org.apache.httpcomponents', name:'httpclient', version:'4.1'
  compile (group:'org.openxri', name:'openxri-client', version:'1.2.1') { excludes([ group: 'org.slf4j', name: 'slf4j-jcl']) }
  compile group:'org.jdom', name:'jdom', version:'1.1'
  compile group:'com.google.collections', name:'google-collections', version:'1.0'
    }

Mapping the openid:  I created a controller called GAMOpenIdController which stands for Google Apps Marketplace OpenID.   Using convention this would have mapped to a url:/GAMOpenId/OpenID where for clarity I would prefer it to look like this:  url:/openid.  This url is the one Google redirects you to  to check a user exists and then to authenticate the application.  In order to do this I had to add an entry into the UrlMappings.groovy configuration file:

"/openid"(controller:"GAMOpenId", action:"openid")

Adding Java SSO Code:  This is one of the reasons Grails is so awesome.  You can embed Java code in with your Groovy code.  Awesome.  So I copied my SSO helper code from my other application.  This is largely based on the marketplace sample application located here.


I have modified this code slightly and will include a full copy of the grails application at the end of this article.

Adding the Controller:  This was so easy.   See the code at the end.

Adding the service:  Services in Grails control the business logic so I added all the servlet code from the marketplace application in here, but modified for Groovy. See file contactpa.GoogleOpenIdAuthService.groovy.  This then called the Java SSO Code listed above.  I didn't really have a problem here but the final stage of authentication was failing.  I fixed this in the method getReceivingUrl().  It turns out when the server makes a call to another server, when the request is returned this is routed to a URL of the format: url:/grails/GAMOpenId/openid.dispatch whereas for the receivingURL to be authenticated correctly it should read url:/openid.

Don't edit applicationContext.xml:  One trap, coming from a Spring background was that I needed to add some DI Beans in my Spring configuration for the Java SSO stuff above.  Instead of following Grails convention I decided to edit the applicationContext.xml, which you would ordinaarily do in a standard Spring app.  However this gave me a whole host of unusual errors until I read that beans should be configured in a file called resources.xml.   So in conf/spring/resources.xml I added my beans:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<bean name="consumerHelperFactory" class="contactpa.sso.ConsumerFactory" />
<bean name="consumerHelper" class="com.google.step2.ConsumerHelper" factory-method="getConsumerHelper"
factory-bean="consumerHelperFactory" /> 
<bean name="httpFetcher" class="com.google.step2.http.DefaultHttpFetcher" />
<bean name="googleHostedHostMetaFetcher"
class="contactpa.sso.GoogleHostedHostMetaFetcher">
<constructor-arg index="0" ref="httpFetcher" />
</bean>
</beans>

Summary

This has turned to be a bit of a mind dump as I navigate a new area in Java development, for me anyhow. Grails is a great natural progression for Java developers looking to get into dynamic programming and using a framework for convention over configuration.  

I have open sourced this project and it is available at git hub to analyse and comment on:

https://github.com/thinkjones/SSO-with-Google-Apps-Marketplace-with-Grails


1 comment:

  1. Hey!

    Probably you don't remember very well about this application you described here, but I said to give it a try.

    I downloaded your code and when I run it under localhost like this:
    http://localhost:8080/SSO/openid?hd=mydomain.com

    it gives me the error:
    Error: invalid_request
    Error in parsing the OpenID auth request.
    Learn more

    I'm struggling with this one and have no clue which might be the problem.

    If you have time to think about an answer, please do it. It would be very helpful for me.

    ReplyDelete