Saturday, 11 February 2012

Struts2 Login filter




Trying to secure your Struts2 web application and googling gives only information in chunks. A single person/blog/site doesn't explain all required things. Moreover, if you are a Netbeans user and unable to understand the hierarchy given in examples in Eclipse? Then this particular post may be of very use to you. I am posting my experience so that people like me may benefit from it in the future and save hours of time.

To implement URL Filter you need to make the following changes in web.xml

web.xml
     <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
   
    <filter>
        <filter-name>URLFilter</filter-name>
        <filter-class>MyFilter</filter-class>
        <init-param>
            <param-name>onError</param-name>
            <param-value>/login.jsp</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>URLFilter</filter-name>
        <url-pattern>/admin/*</url-pattern>
    </filter-mapping>
                                       //other tags here      

As you can see apart from normal Struts2 filter, now we have a custom filter defined by the name URLFilter and this filter applies to all URLs consisting of "admin/xyz.jsp" in the URL.


As is clear from above the class MyFilter corresponds to URLFilter filter and here is the code:

MyFilter.java              
import com.opensymphony.xwork2.ActionContext;
import java.io.IOException;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class MyFilter implements Filter {

    private String errorURL;

    public void init(FilterConfig filterConfig) throws ServletException {
        errorURL = filterConfig.getInitParameter("onError");
        if (errorURL == null) {
            errorURL = "/login.jsp";
        }
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        Map session = ActionContext.getContext().getSession();
        if (session.containsKey("login")) {
            chain.doFilter(request, response);
        } else {
            request.setAttribute("errorMsg", "Authentication required");
            request.getRequestDispatcher(errorURL).forward(request, response);
        }
    }

    public void destroy() {
//        throw new UnsupportedOperationException("Not supported yet.");
    }
}
The above code can easily be searched out using Google. Above I have simply extended the Filter interface and provided very basic implementation of its methods.


This image shows the structure of the project in Netbeans.

adminpage.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="s" uri="/struts-tags" %>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>Hello Admin!</h1>
        <s:a href="../index.jsp">HOME</s:a>
    </body>
</html>

index.jsp
        <s:if test="#session['login']!=null">
            <!--<s:property value="#session['login']"/>-->
            <s:url id="logout" action="logoutaction"/>
            <s:a href="%{logout}">Logout</s:a>
        </s:if>
        <s:else>
            <s:a href="login.jsp">Login</s:a>
        </s:else>
        <s:a href="admin/adminpage.jsp">Admin Page</s:a>

login.jsp
         <s:if test="#request['errorMsg']!=null">
            <s:property value="#request['errorMsg']"/>
        </s:if>

        <s:actionerror/>
        <s:fielderror/>
       
        <s:form action="loginaction" method="post">
            <s:textfield name="username" label="Username" />
            <s:password name="password" label="Password"/>
            <s:submit value="submit"/>
        </s:form>

struts.xml
<struts>
    <!-- Configuration for the default package. -->
    <package name="default" extends="struts-default" namespace="">

        <action name="loginaction" class="actions.MyAction">
            <result name="success">index.jsp</result>
            <result name="error">login.jsp</result>
            <result name="input">login.jsp</result>
        </action>

        <action name="logoutaction" class="actions.MyAction" method="logout">
            <result name="success" type="redirect">index.jsp</result>
        </action>
    </package>
</struts>

 Now the only file left is
MyAction.java
package actions;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;

public class MyAction extends ActionSupport {

    private String username;
    private String password;

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String execute() {
        if (username.equalsIgnoreCase(password)) {
            Map sess = ActionContext.getContext().getSession();
            sess.put("login", true);
            return SUCCESS;
        }
        addActionError("Invalid username/password");
        return INPUT;
    }

    public String logout() {
        Map sess = ActionContext.getContext().getSession();
        sess.remove("login");
        return SUCCESS;

    }
}

Download this project.

So this is the way how we can implement URL Filters in Struts2. Hope so you enjoyed reading.
Let me know if you face problems in the given code. Comments are always welcome.






5 comments:

  1. What if you want your login filter to protect your actions also?

    ReplyDelete
    Replies
    1. The login filter will protect everything including actions, you didn't noticed the filter is set to
      /admin/*
      so all actions within /admin
      will automatically be protected by the filter.

      Moreover, Interceptors are better to use for the purpose you are stating.

      Delete
  2. Thanks for sharing the code. This works fine.
    But I am facing an issue,
    A user not logged in and trying the "admin/adminpage.jsp" URL, as per code its throwing an error "authentication required", good, but in address bar, it is still showing like,
    "http://localhost:8080//admin/adminpage.jsp" instead of "http://localhost:8080//login.jsp"

    Is there any way to change the url while authentication is failed.?

    Thanks in advance,
    Surendar

    ReplyDelete
    Replies
    1. hello Surendar, in case the user is not logged in, the user will be redirected to login.jsp content but the URL still remains same, so in which case you can direct the user to some intermediate page say invalidaccess.jsp, which will through javascript or using META tag will redirect to login.jsp and in this way the URL changes.

      Delete
  3. Using blue color with white background is annoying

    ReplyDelete