Tuesday, July 13, 2010

GWT and Open-ID using Spring Security

In this post I'll combine the GWT and Spring Security integration from http://technowobble.blogspot.com/2010/05/gwt-and-spring-security.html and the Open-ID using Spring Security from http://technowobble.blogspot.com/2010/06/using-spring-securitys-openid.html. I'm assuming you've read them before reading further... :)

I was also inspired by http://www.sociallipstick.com/?p=86 and http://code.google.com/p/dyuproject/wiki/OpenidLoginWithoutLeavingPage to get this working with a pop-up as my sample application is based on GWT - hence, I don't want to direct the user to another page and loose the application state etc.

I'm also showing how to exchange Open-ID attributes with e.g. Google. As with the previous blogposts, the sample application is runnable on Google App Engine.

With no further ado, this is basically what is needed to add Open-ID support to my previous sample application:

From my second post, add Openid4javaFetcher, MyHttpCacheProvider and OpenIdUserDetailsServiceImpl-classes, update the pom.xml with the necessary Open-ID dependencies and add the customized configuration of the OpenIDAuthenticationFilter into applicationContext-security.xml.

Now that all necessary Open-ID stuff is in place, let's start adding the functionality to the application from my first post:

First of all, let's add a "Sign in with Google" button to the LoginDialogBox:

 @UiHandler("googleLoginButton")
 void googleLogin(ClickEvent e) {
  Window.open("/j_spring_openid_security_check?openid_identifier=https://www.google.com/accounts/o8/id", "openid_popup", 

"width=450,height=500,location=1,status=1,resizable=yes");
 }

It will open up a pop-up window posting to the OpenIDAuthenticationFilter, which will take care of the rest of the Open-ID functionality. As discussed in the links mentioned above, I need a callback function that can be called once the Open-ID-request is return, i.e. from the pop-up window to the GWT application window. I therefor made the following changes:

 // general callback for any type of authentication scheme
 RequestCallback callback = new RequestCallback() {
     public void onError(Request request, Throwable exception) {
      showError();
         Log.error(exception.getMessage());
     }
     
     public void onResponseReceived(Request request, Response response) {
         if (response.getStatusCode() == Response.SC_OK) {
          // notify all interested components
          fireEvent(new LoginEvent(true));
          
          // issue the command that triggered the dialog
          if (cmd != null) {
           cmd.execute();
          }
          
          hide();
          
          Log.debug("[success (" + response.getStatusCode() + "," + response.getStatusText() + ")]");
         } else {
          showError();
          Log.error(response.getStatusCode() + "," + response.getStatusText());
         }
     }
 };

 // make the callback function available from JavaScript
 private native void exportMethods(LoginDialog instance) /*-{
  $wnd.handleOpenIDResponse = function(statusCode, statusText) {
   return instance.@com.myappenginecookbook.gwt.client.LoginDialog::onAuthentication(ILjava/lang/String;)(statusCode, 

statusText);
  }
 }-*/;
 
 public void onAuthentication(final int statusCode, final String statusText) {
  // call callback with the needed parameters
  callback.onResponseReceived(null, new Response() {
         @Override
         public String getHeader(String header) {
          return null;
         }

         @Override
         public Header[] getHeaders() {
           return null;
         }

         @Override
         public String getHeadersAsString() {
           return null;
         }

         @Override
         public int getStatusCode() {
           return statusCode;
         }

         @Override
         public String getStatusText() {
           return statusText;
         }

         @Override
         public String getText() {
           return "";
         }
      });
 }

Note that I've broken out the anonymous RequestCallback so that it can be used with both ways of authenticating.

What's left now is to make sure the callback function will be called, which is the responsiblity of the OpenIdAuthenticationFailureHandler and OpenIdAuthenticationSuccessHandler-classes, which will be called by Spring Security at the end of the Open-ID request:

package com.myappenginecookbook.security;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

public class OpenIdAuthenticationSuccessHandler implements
  AuthenticationSuccessHandler {

 @Override
 public void onAuthenticationSuccess(HttpServletRequest request,
   HttpServletResponse response, Authentication authentication)
   throws IOException, ServletException {
  
  response.setContentType("text/html");
  PrintWriter out = response.getWriter();
  
  String html = 
         "<html>" +
      "<head>" +
      "</head>" +
      "<body onload=\"window.opener.handleOpenIDResponse("+HttpServletResponse.SC_OK+",'Authentication accepted');window.close

();\">" +
      "</body>" +
      "</html>";
        
        out.print(html);          
 }
}

The OpenIdAuthenticationSuccessHandler will render a simple html-fragment which utilizes JavaScript to call the exported GWT-method to signal the sucess/failure of the request. The OpenIdAuthenticationFailureHandler is basically identical, but will call the callback with the SC_UNAUTHORIZED http-code instead.

To finalize the upgraded sample app I searched the forums on how to get hold of the exchanged attributes and came up with this thread, and created a CustomOpenIDAuthenticationProvider-class to deal with it:

package com.myappenginecookbook.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.openid.OpenIDAttribute;
import org.springframework.security.openid.OpenIDAuthenticationProvider;
import org.springframework.security.openid.OpenIDAuthenticationToken;

public class CustomOpenIDAuthenticationProvider extends
  OpenIDAuthenticationProvider {

 @Override
 protected Authentication createSuccessfulAuthentication(
   UserDetails userDetails, OpenIDAuthenticationToken auth) {
  
  if (userDetails instanceof CustomUser) {
   CustomUser user = (CustomUser) userDetails;
   for (final OpenIDAttribute attribute : auth.getAttributes()) {
        if ("email".equals(attribute.getName())) {
          String email = attribute.getValues().get(0);
          user.setEmail(email);
        }
      }
  }

  return super.createSuccessfulAuthentication(userDetails, auth);
 }
}

Hope this helps in getting your own applications Open-ID aware! Sourcecode can be found here.

5 comments:

  1. Juan Carlos GonzálezNovember 12, 2010 at 5:51 PM

    Hi Mattias.

    I'm trying to test this approach and I can run the project, but when clicking the Sign in with Google button I get the following stack trace:

    java.lang.NoClassDefFoundError: org/apache/commons/codec/binary/Base64
    at org.openid4java.association.DiffieHellmanSession.publicKeyToString(DiffieHellmanSession.java:306)
    at org.openid4java.association.DiffieHellmanSession.getPublicKey(DiffieHellmanSession.java:206)
    at org.openid4java.message.AssociationRequest.....

    I've removed the rest because of lenght limit.

    In your previous post regarding OpenId, Spring and GAE, you created a pom.xml file witrh several dependencies to commons library. However, this project do not contain the same commons references.

    Please, could you help me with this?.

    K.R.

    Juan Carlos

    ReplyDelete
  2. Juan Carlos GonzálezNovember 16, 2010 at 10:31 AM

    Hi Mattias,

    I've added the following dependency (got it from openid4java pom file) to the pom.xml file and now it works properly. I've deployed to app engine and works as expected.


    commons-codec
    commons-codec
    1.3


    K.R.

    Juan Carlos

    ReplyDelete
  3. Thanks for sharing the solution!

    I was just about to tell you that I can't give you free consultancy hours and support all things I write on my blog... :)

    ReplyDelete
  4. Thanks for the posts. I notice you have several related posts regarding GWT, OpenID & Spring Security. I'm confused as to which is the most current. Can you clarify which is most recent? Also do you happen to have a download of all sample source code? Thanks!

    ReplyDelete
  5. Hi, this is the latest post and combines all three of GWT, Spring Security and Open-ID. The previous posts only used either only GWT/Spring Security or Spring Security/Open-ID.

    All source code should be linked in the posts - see the link at the end of this post, for example.

    Thanks for reading!

    ReplyDelete