Thursday, August 4, 2011

Integrating OpenId into my Struts 2 app - Part 1 - Interceptors

My next few blog posts will detail the integration of OpenID authentication into my application. The OpenId website describes the benefits of using OpenId.  As a web site user myself, I'm always relieved when I'm able to reuse my existing Google account when creating new a profile on a website.  That's one less User Id and Password I'll need to write down or remember.  I also appreciate that I'm safely authenticating with the OpenId provider and the underlying website never has access to my password.

Before I begin, I want to list and give props to several websites that provided invaluable information and code samples that greatly assisted me in integrating OpenId into my own application.

Struts 2 Interceptors

OpenId4Java Code samples

I'm not going into detail on how Interceptors work in Struts 2.  I suggest reading the relevant Struts 2 docs and the links I posted above.

Here's the code for the LoginInterceptor:
public class LoginInterceptor extends AbstractInterceptor implements StrutsStatics{
    
    static Logger logger = Logger.getLogger(LoginInterceptor.class);

    public String intercept(final ActionInvocation invocation) throws Exception {
        logger.debug("Entering intercept()");

        String invocatedAction = invocation.getAction().getClass().getName();
        logger.debug("Invocated Action: " + invocatedAction);

        // get references to the App Context, Session, and Request objects
        final ActionContext context = invocation.getInvocationContext ();
        HttpServletRequest request = (HttpServletRequest) context.get(HTTP_REQUEST);
        HttpSession session =  request.getSession (true);

        // Is there a "user" object stored in the user's HttpSession?
        User user = (User)session.getAttribute("user");
        
        if (user == null) {
            // The user has not logged in yet.
            logger.debug("User NOT found in the Session");

            // Is the user attempting to log in right now?
            String loginIdentifier = request.getParameter("openid_identifier");
            String openIdEndpoint =  request.getParameter("openid.op_endpoint");

            // we can know the user is trying to login right now if the "openid_identifier" is a Request parm
            if (! StringUtils.isBlank (loginIdentifier) ) {
                // The user is attempting to log in.
                logger.debug("The user is attempting to log in");

                // Process the user's login attempt.
                return invocation.invoke ();
            }
            // we know the user has just auth'd with the OpenID provider if the "openid.op_endpoint" is a Request parm
            else if(! StringUtils.isBlank (openIdEndpoint) ) {
                // The user has logged in with an OpenId provider
                logger.debug("The user has logged in with an OpenId provider");

                // Process the user's login attempt.
                return invocation.invoke ();
            }
            else {
                // save the original URL, we'll need it later
                saveReceivingURL(request, session);
                
                logger.debug("Forwarding to the Login form");
            }

            // it we get this far then the user hasn't tried to login yet, 
            // and we need to send to the login form.
            return "login";
        } 
           else {
               logger.debug("User " + user.toString() + " found in the Session");
               
               // user is already logged in
               return invocation.invoke ();
        }
    }

    private void saveReceivingURL(HttpServletRequest request, HttpSession session) {
        logger.debug("Entering saveReceivingURL()");
        
        // extract the receiving URL from the HTTP request
        final StringBuffer receivingURL = request.getRequestURL();
        final String queryString = request.getQueryString();

        // if there is a query string then we'll need that too
        if (queryString != null && queryString.length() > 0) {
            receivingURL.append("?").append(request.getQueryString());
        }
        
        logger.debug("Original URL: " + receivingURL.toString());
        
        // save the original URL in the Session
        // we're going to need to redirect the user back to this URL after login is completed
        session.setAttribute("originalURL", receivingURL.toString());
    }
}

The LoginInterceptor is invoked for every request and checks the http session to see if the user is already logged in.  If the user is found in the session, the Interceptor simply returns invocation.invoke () and allows the request to process normally.

If the user is not found in the session then we need to interrupt the request and force the user to authenticate before returning to the originally requested resource.  However, it's not as simple as just forwarding the user to the login page.  Remember, the Interceptor is invoked on every single request and we need to account for the following:
  • Is the user presently already trying to login?  If so, there's no need to interrupt the request because we're already going to the login form.
  • Is the request coming from the OpenId Provider (Google, Yahoo, etc)?  If so, again there's not need to interrupt the request.  Allow the request to proceed to our app's authentication code where we'll work with the response from the OpenId Provider.
  • Otherwise, we'll go ahead and interrupt the request and forward the user to the login form where they can choose which OpenId Provider they want to use for authentication.  Note: We'll need to keep a reference to the original requested resource so that we can send the user to it when the authentication is completed.
Now that we have the code for our Interceptor, we'll need add it to our Struts configuration and modify the Interceptor Stack.

        <interceptors>
            <interceptor name="hibernateSession" class="org.robbins.flashcards.presentation.HibernateSessionInterceptor"/>
            <interceptor name="loginInterceptor" class="org.robbins.flashcards.presentation.LoginInterceptor"/>

               <interceptor-stack name="defaultStackWithStore">
                   <interceptor-ref name="hibernateSession"/>
                   <interceptor-ref name="loginInterceptor"/>
                <interceptor-ref name="store">
                    <param name="operationMode">STORE</param>
                </interceptor-ref>
                <interceptor-ref name="defaultStack" />
            </interceptor-stack>
            
               <interceptor-stack name="defaultStackWithRetrieve">
                   <interceptor-ref name="hibernateSession"/>
                   <interceptor-ref name="loginInterceptor"/>
                <interceptor-ref name="store">
                    <param name="operationMode">RETRIEVE</param>
                </interceptor-ref>
                <interceptor-ref name="defaultStack" />
            </interceptor-stack>
        </interceptors>

Since I'm also using the "store" interceptor I've had to configure two interceptor stacks to deal with the Storing and Retrieving operations.  Below is an example of a an action declaration:

        <action name="*"
                class="org.robbins.flashcards.presentation.FlashCardAction"
                method="{1}">
               <interceptor-ref name="defaultStackWithRetrieve"/>
            <result name="success" type="tiles">{1}flashcard.tiles</result>
            <result name="error" type="tiles">error.tiles</result>
        </action>

In my next post, I'll discuss the JQuery OpenID Selector component into my Login form.

1 comment:

  1. Good!
    But may be better/simple do auth by servlet filters? The process of authentication will create a user object in the session. There is no different how to do it. But OPENID auth work with a low level http rules and it is very difficult process with struts.

    ReplyDelete