Vita Rara: A Life Uncommon

Creating a Login Interceptor in Struts 2


Categories: | |

Controlling access to web resources with a a login process is a common use-case. Implementing this using an interceptor in Struts 2 is very straight forward.

The parts of the solution:

  • An interceptor class,
  • An interceptor definition in our struts.xml file,
  • An interceptor stack that uses our interceptor in our struts.xml file,
  • A global result in our struts.xml file,
  • A JSP page to accept the username and password,
  • A service bean to process our user's login attempt,
  • A way to wire our service bean to our interceptor.

Implementing the LoginInterceptor

In Struts 2 interceptors are called one after the other in a configured order for each invocation of an action class. The interceptors have an opportunity to interact with the input from your user, and to interact with the action after it has completed it's processing. They can also intercept the process between the running of your action and the hand off to the result, but I we will not need that for our purposes here.

The main method in an interceptor is the intercept() method. This method is passed an ActionInvocation object, which contains the action, and the context for the action, such as the HttpRequest, HttpSession, etc. The intercept method returns a String that maps to a "result" in our struts.xml file.

The intercept() method for our interceptor uses the HttpSession to determine if the user's "user" object is stored in the session. If the user object is in the HttpSession, then we consider the user to be logged in, and continue the chain of the interceptor stack by calling invocation.invoke ().

If the user's "user" object isn't in the HttpSession we check to see if the user is attempting to log in, and process the login. If they're username and password validate the interceptor sends them the to the "login-success" result, otherwise we send them the "login" result.

public String intercept (ActionInvocation invocation) throws Exception {
    // Get the action context from the invocation so we can access the
    // HttpServletRequest and HttpSession 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?
    Object user = session.getAttribute (USER_HANDLE);
    if (user == null) {
        // The user has not logged in yet.

        // Is the user attempting to log in right now?
        String loginAttempt = request.getParameter (LOGIN_ATTEMPT);
        if (! StringUtils.isBlank (loginAttempt) ) { // The user is attempting to log in.

            // Process the user's login attempt.
            if (processLoginAttempt (request, session) ) {
                // The login succeeded send them the login-success page.
                return "login-success";
            } else {
                // The login failed. Set an error if we can on the action.
                Object action = invocation.getAction ();
                if (action instanceof ValidationAware) {
                    ((ValidationAware) action).addActionError ("Username or password incorrect.");
                }
            }
        }

        // Either the login attempt failed or the user hasn't tried to login yet, 
        // and we need to send the login form.
        return "login";
    } else {
        return invocation.invoke ();
    }
}

The actual validation of the username and password are left to the processLoginAttempt() method. The processLoginAttempt() method is responsible for using whatever means necessary to validate the username and password and saving the user's user object in the session. This is done this way, because there might be many ways to do the validation, and you might collect additional information on the user to store in their sesion, such as roles, or other credentials. Breaking this out allows you to override this method in your own interceptor and process this however you'd like.

The default implementation uses a SecurityManager to lookup the user's information.

/**
 * Attempt to process the user's login attempt delegating the work to the 
 * SecurityManager.
 */
public boolean processLoginAttempt (HttpServletRequest request, HttpSession session) {
    // Get the username and password submitted by the user from the HttpRequest.
    String username = request.getParameter (USERNAME);
    String password = request.getParameter (PASSWORD);

    // Use the security manager to validate the user's username and password.
    Object user = securityManager.login (username, password);

    if (user != null) {
        // The user has successfully logged in. Store their user object in 
        // their HttpSession. Then return true.
        session.setAttribute (USER_HANDLE, user);
        return true;
    } else {
        // The user did not successfully log in. Return false.
        return false;
    }
}

Declaring our Interceptor in struts.xml

In our struts.xml configuration file we need to declare the LoginInterceptor, and add it to an interceptor class so it will be used.

<struts>
    <package name="my-default" extends="struts-default">
        <interceptors>
    
            ...

            <interceptor name="login" class="loginInterceptor" />
    
            ...

        </interceptors>
                

    </package>
</struts>

Notice that the fully qualified classname is not declared for the interceptor. We configure that in our Spring config, which allows us to wire our SecurityManager into it.

Configuring our Interceptor Stack to use the LoginInterceptor

We need to add the LoginInterceptor to an interceptor stack so it will be used when an action is invoked.

<struts>
    <package name="my-default" extends="struts-default">
        <interceptors>
    
            ...
    
            <interceptor-stack name="defaultLoginStack">
                <interceptor-ref name="servlet-config" />
                <interceptor-ref name="params" />
                <interceptor-ref name="login" /> <!-- Our LoginInterceptor -->
                <interceptor-ref name="prepare" />
                <interceptor-ref name="chain" />
                <interceptor-ref name="model-driven" />
                <interceptor-ref name="fileUpload" />
                <interceptor-ref name="static-params" />
                <interceptor-ref name="params" />
                <interceptor-ref name="conversionError" />
                <interceptor-ref name="validation" />
                <interceptor-ref name="workflow" />
            </interceptor-stack>

            ...

    </package>
</struts>

Adding the Global "login" Result

We need to add a global "login" result so that it is available no matter what protected action a user might invoke. This allows our interceptor to return "login" and have it properly mapped to our login page.

<struts>
    <package name="my-default" extends="struts-default">

        ...
        
        <global-results>
            <result name="login">/WEB-INF/pages/Login.jsp</result>
            <result name="login-success">/WEB-INF/pages/Index.jsp</result>
        </global-results>

        ...

    </package>
</struts>

The Complete struts.xml Package

<struts>
    <package name="my-default" extends="struts-default">
        <interceptors>

            <interceptor name="login" class="loginInterceptor" />

            <interceptor-stack name="defaultLoginStack">
                <interceptor-ref name="servlet-config" />
                <interceptor-ref name="params" />
                <interceptor-ref name="login" />
                <interceptor-ref name="prepare" />
                <interceptor-ref name="chain" />
                <interceptor-ref name="model-driven" />
                <interceptor-ref name="fileUpload" />
                <interceptor-ref name="static-params" />
                <interceptor-ref name="params" />
                <interceptor-ref name="conversionError" />
                <interceptor-ref name="validation" />
                <interceptor-ref name="workflow" />
            </interceptor-stack>

            <interceptor-stack name="defaultInsecureStack">
                <interceptor-ref name="servlet-config" />
                <interceptor-ref name="params" />
                <interceptor-ref name="prepare" />
                <interceptor-ref name="chain" />
                <interceptor-ref name="model-driven" />
                <interceptor-ref name="fileUpload" />
                <interceptor-ref name="static-params" />
                <interceptor-ref name="params" />
                <interceptor-ref name="conversionError" />
                <interceptor-ref name="validation" />
                <interceptor-ref name="workflow" />
            </interceptor-stack>
        </interceptors>

        <!-- Make the defaultLoginStack the default one used
                for all actions unless otherwise configured. -->
        <default-interceptor-ref name="defaultLoginStack" />

        <default-action-ref name="index" />

        <global-results>
            <result name="login">/WEB-INF/pages/Login.jsp</result>
            <result name="login-success">/WEB-INF/pages/Index.jsp</result>
        </global-results>

        <action name="index">
            <result name="input">/WEB-INF/pages/Index.jsp</result>
            <result name="success">/WEB-INF/pages/Index.jsp</result>
        </action>

    </package>
</struts>

A Login Page

We need a JSP page to allow the user to enter their username and password.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Login</title>
</head>
<body>
<s:actionerror />
<s:form namespace="/" action="index.action" method="post">
    <s:hidden name="QUADRAN_LOGIN_ATTEMPT" value="%{'1'}" />
    <s:textfield name="QUADRAN_USERNAME" label="Username" />
    <s:password name="QUADRAN_PASSWORD" label="Password" />
    <s:submit value="Login" align="center">
        <s:param name="colspan" value="%{2}" />
        <s:param name="align" value="%{'center'}" />
    </s:submit>
</s:form>
</body>
</html>

Notice the hidden QUADRAN_LOGIN_ATTEMP property in the form. That is what triggers the processing of the login by the LoginInterceptor.

The SecurityManager Service Bean

I use plain POJO service beans and wire them using Spring. Here's a simple SecurityManager interface. Just enough to do the login. I leave the rest as a practice to the user.

package net.vitarara.quadran.core.business.api.security;

public interface SecurityManager {

    public Object login (String username, String password);
}

And the implementation in Groovy:

package net.vitarara.quadran.core.business.impl.security;

import net.vitarara.quadran.core.business.api.security.SecurityManager;

class SecurityManagerImpl implements SecurityManager {

    def partyDao;

    Object login (String username, String password) {
        return partyDao.findByUsernameAndPassword (username, password);
    }
}

I delegate the lookup to my PartyDao which is a class for looking up all types of parties, users, customers, vendors, etc. You will need to figure out how you want to look users up in your system.

Wire it Together with Spring

In my Spring applciationContext.xml file I have the following entry for my interceptor. This entry wires my SecurityManager into the interceptor allowing it to delegate the actual lookup to my business service beans.

<bean id="loginInterceptor" scope="singleton" class="net.vitarara.quadran.core.web.util.LoginInterceptor">
    <property name="securityManager"><ref local="securityManager" /></property>
</bean>

If you are using the Struts 2 Spring plugin whe Struts attempts to load the interceptor at startup it will use Spring to instantiate it.

Conclusion

It's a few steps, but the basics are fairly straight forward.

  • Create your interceptor,
  • Configure it in struts.xml,
  • Wire a service bean into the interceptor to do the actual work.

Using this same pattern you can address many cross cutting issues in Struts 2. Such as:

  • Configuring menus,
  • Checking user permissions to access pages,
  • Marshalling the user object from the session to a property on your action.

The interceptor pattern is a very powerful one, well worth looking at. Struts 2 uses it to implement a great deal of its functionality.

AttachmentSize
LoginInterceptor.zip3.31 KB

configuring intercepter before action execution

Hi,
can any body tell me how we can configure intercepter to execute only before or after executing the action class not at both the times.

thanks
usman.sk

Hi, And what if i want

Hi,

And what if i want protect some URL's for a kind of user and others URL's for another kinds of user??

What should i do??

thanks!!

unit testing the LoginInterceptor

Hi,

How do I unit test this interceptor?
Presently following the approach given in the link below,
for Unit testing my actions.Let me know if there is another
way to do the same.

http://depressedprogrammer.wordpress.com/2007/06/18/
unit-testing-struts-2-actions-spring-junit

Thanks,
AD

Interceptor for Cross-site scripting

Hi Friends,

Has anyone tried writing a interceptor for checking Cross-site scripting?
If the URL of the application is manually changed before submitting then it should check if the request is correct before executing it.
For example - URL
www.abc.com/action1.action?alert("XSS security lapse")

Above URL should not be executed successfully.

Can anyone help me or provide sonme ideas on it?

Regards,
Rajiv

What about direct JSP requests from browser

Will this solution protect direct JSP requests from the browser. Say suppose if i point my browser as follows:

http://localhost:9090/Struts2Sample/Login.jsp

Assume my project name is Struts2Sample running on port 9090.

The custom interceptor is not running in this case.

I want the interceptor to run for any web resource request.

Thanks in Advance.

Mahendra Babu R

Same Problem..

Even me too facing the same problem...

can anyone help us??

how can we make interceptor to work for all the web resource requests?

Hide your JSPs behind /WEB-INF/

You place all your jsp files in a directory behind /WEB-INF.

Expose only you default index.jsp that redirects to an index.action. That way the interceptor is always called.

Your jsp pages are not visible unless presented by your actions as a result.

GREAT!!

GREAT ARTICLE!!!

ntkyo

I am new to web programming..

Im trying to create my own securityManager...

Object user = securityManager.login (username, password);

What exactly does securityManager.login return?

an Object??

what kind of object is this!

For example my securityManger is a simple function that has a Hashmap storing user name and password,

If the username and password matches, What should i return?

THanks!

securityManager.login return

I'd either return a Boolean or the user bean. I think the user bean is a better idea. Having it you can then save it in the session for future reference.

security problem

The only flaw that I see on this implementation is that the password is being transmitted as plain text. Consider using a javascript MD5 implementation (http://pajhome.org.uk/crypt/md5/index.html) before sending the form to the server.

Use HTTPS

Not a problem if you use https.

Sending it as MD5 is really not a solution as it can still be treated like plain text and the request may be spoofed.

great article

This is really great article. it is exactly what i am looking for my login implementation. Users Comments are very helpful.
keep blogging.....

Thanks,
Chandra

Unable to get request parameters in Action Class

Hi,
I am doing task on struts2. In that I have to use custom AutherizationInterceptor. In my custom Interceptor Im checking weather user is in session or not. And if he tries to attempt login I gave parameter in struts.xml as true So Im checking against it and interceptor redirects action to Home. But my problem is unable to get the request parameters in Home Action class.
Please help me I need to implement this code in my project.

My code is-------

In struts.xml for Login Attemp

<action name="home" class="com.mss.HomeAction">

<interceptor-ref name="authorizationInterceptor">
<param name="loginAttempt">true</param>
</interceptor-ref>

<result name="input">login.jsp</result>
<result name="login">login.jsp</result>
<result name="success">home.jsp</result>
</action>

My Interceptor -------

public class AutherizationInterceptor implements Interceptor,StrutsStatics {

//private HttpServletRequest httpServletRequest;
private boolean loginAttempt;
/** Creates a new instance of AuthorizationInterceptor */
public AuthorizationInterceptor() {
}
public void destroy() {
}
public void init() {
}

public String intercept(ActionInvocation actionInvocation) throws Exception {

final ActionContext context = actionInvocation.getInvocationContext();

Map session = context.getSession();
Action action = null;

if(loginAttempt) {
return actionInvocation.invoke();
} else {
if(session.get("userName") != null){
action = ( Action ) actionInvocation.getAction();
return actionInvocation.invoke();
} else {
return Action.LOGIN;
}
}
}

public void setLoginAttempt(boolean loginAttempt) {
this.loginAttempt = loginAttempt;
}

My Action Class*****

public String execute() {
resultType = SUCCESS;

String userName = getUserName();
System.err.println("hai ********");
String password = getPassword();
if(userName!=null && password != null) {
request.getSession().setAttribute("userName",userName);
request.getSession().setAttribute("password",password);
resultType = SUCCESS;
} else {
resultType = INPUT;
}
return resultType;
}

thanks in advance
Praveen

same here

am having the same problem , can anybody help me?

avoiding dependency on Servlet classes

If you want to avoid dependencies on Servlet classes for the request and response, you can also have your interceptor class implement SessionAware and ParameterAware. This does introduce a couple of boilerplate methods, which I've noticed you don't care for, but those replace the equally boilerplate code to get the context and then the request and response that way, so I think it balances.

Here is where you find that

Here is where you find that string value:

org.apache.struts2.StrutsStatics.HTTP_REQUEST

More info here:
http://struts.apache.org/2.x/struts2-core/apidocs/constant-values.html

Thanks

This is an awesomely helpful tutorial. Thanks!

Trouble running Custom Interceptor

I am attempting to write a custom interceptor to redirect certain actions to use SSL. I have followed all of the instructions above (except with my own method). My interceptor class is being initialized at startup, but the intercept method isn't being run.

Could you offer any help?

SSL interceptor

I am looking for this as well.

questions

I don't see the place where you can configure which URLs are protected by this solution? what if you don't want to protect everything.

I think it would be helpful showing an example that used ACEGI from Spring.

Use struts.xml

The URLs are determined by what interceptor stack you configure for your action.

Personally I place all of my protected action in a package and use a default interceptor stack that includes the login interceptor. For non-protected actions I just use an interceptor stack that doesn't include the login interceptor.

Mark

redirect the usere to where he wants to be

2 Questions:

1. After a successful login I don't want to redirect the user to a hardcodes page (as you do) but i want the user to be redirected where he wanted to go. E.g. if a user clicks on 'sell' he will be forced to log in by the interceptor and after login the sell-page should be shown. If he vlicks on buy the buy-page should be shown after login. Is there an elegant way to do this?

2. Is there i nicer way to access the session? Access the session in an action is very easy (implement SessionAware) and you have a simple map to get and set values. When working with HttpSession i dont't have a map. I'm allways writing a service that just gets a value out of the sessionattributes and does all the casting for me. Now with struts2's actions and interceptors i need to write that service two times. one works with HttpSession, one with map. Am I missing something?

2 answers

1. Before you redirect the user to the login page, store the original address of the request to the session (something like session.put("going_to", address)). After the login you can then inspect the session redirect the user to the andress. Don't forget to remove the address from the session, so that the user won't be mistakenly redirected again.

2. Yes, see the struts 2 mailreader app for best practises:

Map session = actionInvocation.getInvocationContext().getSession();

You don't need to cast the object to a HttpSession...

Thanks a lot! So i have a

Thanks a lot!

So i have a new version of the interceptor-method:

    public String intercept (ActionInvocation invocation) throws Exception {
        //get the session
        Map attributes = invocation.getInvocationContext().getSession();

        // Is there a "user" object stored in the user's HttpSession?
        Object user = attributes.get(USER_HANDLE);
        if (user == null) {
            // The user has not logged in yet.

            // Is the user attempting to log in right now?
            Map parameters = invocation.getInvocationContext().getParameters()
            
            String loginAttempt = parameters.get( LOGIN_ATTEMPT);
            if (! StringUtils.isBlank (loginAttempt) ) { // The user is attempting to log in.

                // Process the user's login attempt.
                if (processLoginAttempt (request, session) ) {
                    // The login succeeded send them the login-success page.                    
                    return "login-success";
                } else {
                    // The login failed. Set an error if we can on the action.
                    Object action = invocation.getAction ();
                    if (action instanceof ValidationAware) {
                        ((ValidationAware) action).addActionError ("Username or password incorrect.");
                    }
                }
            }

            //put the address of the action called originally into the session
            String urlGoingTo = invocation.getProxy().getNamespace()+"/"+
            invocation.getProxy().getActionName()+".action";
        
            attributes.put( "GOING_TO", urlGoingTo);
            // Either the login attempt failed or the user hasn't tried to login yet, 
            // and we need to send the login form.
            
            
            return "login";
        } else {
            return invocation.invoke ();
        }
    }

It's a little nicer looking because it gets maps of the parameters and session-attributes instead of using the more clumpsy HttpSession/HttpServletRequest-classes.

The url is saved into the session (GOING_TO) and can be used in the login.jsp to redirect to where the user wanted to go originally.

Parameters

I might have missed something here but when I use this code I would loose the parameters from the original request, ie. /namespace/myaction.s2?someparam=12345 would result in /namespace/myaction.s2

Here is what I came up with for forwarding the request with the parameters intact.


    	String namespace = actionInvocation.getProxy().getNamespace();
    	String actionName = actionInvocation.getProxy().getActionName();
    	String requestString = "";
    	
    	Map session = actionInvocation.getInvocationContext().getSession();
    	Map parameters = actionInvocation.getInvocationContext().getParameters();

    	int count = 0;
    	Iterator parametersItr = parameters.entrySet().iterator();
    	while (parametersItr.hasNext()) {
			Map.Entry entry = (Map.Entry) parametersItr.next();

			// add the parameter onto the request string
			Object parameterKey = entry.getKey();
			String[] parameterValue = (String[])entry.getValue();
			for(int j = 0; j < parameterValue.length; j++){
				// if it's the first parameter add a '?' else add a '&'
				requestString += (count == 0) ? "?" : "&";
				
				//get the parameter at this point
				requestString += parameterKey + "=" + parameterValue[j].toString();
			}
			count++;
		}

    	String urlGoingTo = namespace + "/" + actionName + ".s2" + requestString;


What about the parameters

I see you pull out the parameters map but you don't do anything with it. Forwarding to the urlGoingTo isn't going to work well if the original intercepted url is something like whatever.action?myParamId=43.

You can wire login-success

You can wire login-success to a login redirect page in which you will insert this saved address.

Something like: (I use freemarker as a template engine)

...
<META HTTP-EQUIV="Refresh" CONTENT="0; URL=${Session.GOING_TO}">
...

Who is this?

I like your version of the interceptor. What's your name? Also, could you attach this again with a license statement?

Thanks,

Mark

Licence: you can buy the

Licence: you can buy the right to use my code. My bank account number is: 938... :-D

It's just a little change to your code. So take it as a present ;-)

thnx
kartoffelsack

What about Acegi

This seams a very nice solution within struts2. I'm however more inclined to using acegi as a seperate module in integrating both (inthe correct filer order) in web.xml only. E.g. http://forum.springframework.org/showthread.php?p=132896. Any thoughts anyone?

Not working ...

Unfortunetely, if you specify wildcards in action name like this:

[action name="news/*/*" method="{1}" class="newsAction"]
[result name="success" type="tiles"]news.view[/result]
[result name="show" type="tiles"]news.view[/result]
[result name="list" type="tiles"]news.list[/result]
[result name="redirectList" type="redirect-action"]news/list[/result]
[param name="id"]{2}[/param]
[/action]

This interceptor won't be even initialised.

Any ideas?

RE: Source code



Hi, could you please forward the entire source code of the application to my Email ... because i am little but confusing with this code

My mail id

mr.java007@gmail.com

Regards
Marimuthu

Thanks

Hi, The example above one is nice and good

It is working fine for me

Thanks to Vita Rara

Multi-User Support

session.setAttribute (USER_HANDLE, user);

Am I missing something or do the solutions on this page not support multi-user access? A session is shared across all users so if each user is setting 'USER_HANDLE', surely that will override the existing user object stored in 'USER_HANDLE' leading to all kinds of security loopholes?

One session per user

Each user gets his/her own session, so there is no loophole (due to shared sessions). Since browsers don't share sessions even if they are on the same computer you can easily test this if you access the site from two browsers (eg FF and IE).