Showing posts with label spring MVC. Show all posts
Showing posts with label spring MVC. Show all posts

Monday, December 13, 2010

OpenID + Spring MVC 3 + Spring Security 3 + OpenID Selector

We decided to move our authentication into openID. Meanwhile, we upgrade our security from Spring Security 2 to 3. Adding OpenID to spring security is straightforward, add the following tag into "<http>" tag of security.xml
<openid-login login-page="/login.jsp" login-processing-url="/openlogin" user-service-ref="userDao"
                 authentication-failure-handler-ref="openIdAuthFailureHandler"/>

And OpenID will take over the authentication.

However, there are more to change for openID.

First and most obviously, we need a new login page takes user to openid provider instead of our own user/password checker. We chose openid-selector (1.2 currently) as the spring security choose it for demo. It is a fairly nice package. It allows one to choose from several openid providers (Google, Yahoo, AOL, OpenID, blogger, flicker, ...) Several things need to be done to use it:


  • However, it uses jQuery and has lots of conflict with prototype we are using. Fortunately, the conflict originates mostly from "$". I modify the two js file, replace all "$" with "jQuery" and add jQuery.noConflict(); in the beginning and firebug stops to complain.


  • There are several options in openid-jquery.js to play with. I use default mostly, except "no_sprite" to true as I have uncomment flickr and its not in the big sprite picture.


  • In the login page, add following into "<head"> tag (notice that openid-jquery.js should be add before openid-jquery-en.js)
    <script type="text/javascript" src="<c:url value="/scripts/jquery/openid-selector/js/openid-jquery.js" />"></script>
         <script type="text/javascript" src="<c:url value="/scripts/jquery/openid-selector/js/openid-jquery-en.js" />"></script>

    and add following into "<body">
    <script type="text/javascript">
            jQuery.noConflict();
            jQuery(document).ready(function(){
                openid.img_path="<c:url value='/scripts/jquery/openid-selector/images/'/>";
                openid.init("openid_identifier");
                jQuery("#openid_identifier").focus();
            });
        </script>

    Notice the openid.img_path has to be set outside if the openid-selector files are not put under the root.

These pretty much take care of login. But now we also need to modify signup process. Originally, you click signup and then put a new username/password. Now with openid, you first login with an openid from one of the providers, system found that you are not a user, and provide you a signup sheet. That is why in above <openid-login"> tag, we need a special "openIdAuthFailureHandler" for "authentication-failure-handler".

We can extend a spring handler for this purpose.
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.imirsel.nema.webapp.security;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.openid.OpenIDAuthenticationStatus;
import org.springframework.security.openid.OpenIDAuthenticationToken;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;

/**
 *  Customized {@link AuthenticationFailureHandler} that redirect to sign-up page
 * if the OpenID authentication succeeds, but the user name is not yet in local DB of the container
 * @author gzhu1
 */
public class OpenIDAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    static private Log logger = LogFactory.getLog(OpenIDAuthenticationFailureHandler.class);

    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException exception)
            throws IOException, ServletException {
        logger.error(exception, exception);
        if (exception instanceof UsernameNotFoundException
                && exception.getAuthentication() instanceof OpenIDAuthenticationToken
                && ((OpenIDAuthenticationToken) exception.getAuthentication()).getStatus().equals(OpenIDAuthenticationStatus.SUCCESS)) {
            DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
            request.getSession(true).setAttribute("USER_OPENID_CREDENTIAL", exception.getAuthentication().getPrincipal());
            // redirect to create account page

            logger.info("user (" + exception.getAuthentication().getPrincipal() + "," + exception.getExtraInformation() + ") is not found and redirect to signup.");
            redirectStrategy.sendRedirect(request, response, "/signup.html");

        } else {
            super.onAuthenticationFailure(request, response, exception);
        }
    }
}

and declare the bean in applicationContext.xml.
<bean id="openIdAuthFailureHandler" class="org.imirsel.nema.webapp.security.OpenIDAuthenticationFailureHandler">
        <property name="defaultFailureUrl" value="/login.jsp"/>
    </bean>

Of course, now I do not really have a password, so I generate a random string for password, because a password is needed somewhere else.

That is it. Now your user can sign-in with their Google, Yahoo, AOL, open-id account directly.

Sunday, June 27, 2010

Spring Webflow

I have been using it for maybe two months and probably can say a little about it now. It is not our first choice. That would be GWT. We started NEMA mostly with the traditional Spring MVC. Amit, the leading programmer in NEMA, familiar with it, although he leaved the binding/command part out of the picture. So we used it kind of strictly of MVC model. I did not use spring mvc before, but I used Struts 1 and Spring in last project and the concepts of MVC and Inverse of Control (IoC) is not unfamiliar. I mostly do the patching work for Amit, adding a new controller, modify the existing one, etc.

Later, we need a more wizard like function, and Amit envision a site with more Ajax-like functions, such as status bar, ... So he asked me to look into GWT. But I am not familiar with it and things get bogged down as I learned it. Mostly, I felt a bit uncomfortable with GWT because it lacks the mature frame work like Spring MVC, everything backs to JSP/servlet stage. Also, it depends on CSS heavily and none of us are comfortable with it. But if it is now, I might stick longer and be more patient with it and maybe we should use it. Anyway, we cut it loose and here comes Spring Webflow.

The other senior programmer, Andrew used webflow before and he likes it. So we adopted it for its natural fit for wizard function. And in some sense, we just want to try a new tool. The webflow journey started.

It is fun experience with it. A little rough at the beginning, the first few weeks I always feel close, but something keeps popping up. Amit was kind of pissed off by my promising and not delivering. But finally, we delivered. And now it works fairly well.

A few things that I would like to say about spring webflow:

  • It can use the POJO as actions, which makes me feel better and excellent for testing. Webflow provides quite some convenience to facilitate this. I rely heavily on POJO and shift quite some logic into the webflow definition. This makes the definition a bit clumsy, but it is probably a good price to pay.
  • The history function does not work from suflow. See my post in their forum. Spring forum is kind of frustrating. No reply, even no view.
  • Some small things that are not well-documented. Amit upgraded it to 2.1.0Milestone and does not tell me. It switched to Spring EL where it uses #() instead of $(). I dig around quite a while to figure it out.


It also comes a Spring JS by default bundled with Dojo, which is part of the reason I started Dojo. I am going to look into it.

Saturday, June 26, 2010

A Quest for Better Json

We use json for the ajax call in NEMA DIY. And it is an interesting journey for us to find a way to implement it.

For my last project, I used a small xml java generating library for AJAX. This time, we need to generate more and we need json instead. (Amit likes Json more.) In the very beginning, we rolled our own and use some library (XStream I think) to write directly to HttpServletResponse. After a while, it becomes quite tedious, so I looked around and find Spring-Json. It is kind of nice to use with Spring MVC, I only need to register a special view for Json and push models in the same view as the normal jstl/jsp way. We are happy for a couple of months.

We upgraded to Spring 3 in May and Amit switch to the Jackson Json view that bundled with Spring 3. It works in the same way as Spring-Json because Spring-json is based on Spring 2.5. But we met some problems soon. Jackson does not handle the circular reference very well. Spring-json's default engine SOJO simply break the reference in some level and that serves us well enough. Jackson can works with proper annotation in this situation but we do not like to modify the code for this purpose and furthermore, we might need two different versions of Json serialization for the same class. So I have to reinstate spring-json and puts lot of exclusion in the maven dependency description.

We are not very satisfied with the situation, especially Amit. He does not like spring-json because its dependency on spring 2.5, which might break down with our majorly spring 3 stuff. But he has to live with it as I am the guy doing the front-end now. But deep down, I am a little uncomfortable with it either. All these tools are a little over-engineering for us because they are designed for bi-direction purpose that supports both serialization and de-serialization. But we only need simple serialization in Java and deserialization happens in Javascript. While we do have some very complicated classes that needs to be sent via json, we only need part of the information, which can be in a fairly simple form, not the complicated model that those framework engineered for.

I started to look into Dojo and would like to switch to its datagrid. It can hook with its data stores and their reference store is Json one. Their documents are limited and little weird. I made some experiments and it seems that the dadagrid only handles the array of flat json objects. So I am back to square one and has to work on the json presentation. I tried to work within spring-json to filter out some more complicated but not useful. Not working! So back to XStream. It is hard. I tried several things, even looked into its source. That is an interesting story by itself. Now I back to write to response directly. I ended up wrote some customized converter to plug-in for certain types.

And it breaks down when I tried to write two or more models once. I just cannot get it work. (From the hindsight, I might be able to do it by push it as Map<Spring,Object>. Well, I tested it and it does not worked out well. ) Finally, I have to try the jsnoview. Now I decide to do some really basic thing. I push the object in as a Map<String,String>, and array as the list of it. it works just fine with jsonview. Actually it works better because now I can push in a processed value instead of the raw value to be processed in javascript. It occurs to me that maybe the Jackson json works with it as well. I tested it and it works just fine. Now we are using Jackson view and home-made pre-processing. Evething seems to work. I am fairly happy.

I guess the moral of it is that sometimes it is better to just do it by myself and it might be the simplest approach. I spent maybe 50hrs on the source, document of xstream, spring-json and it is kind of wasted. They are good for what they are set to do, but their focus often do not align with mine well and therefore hard to use. Next time looks out!

What a journey!