Tuesday, March 20, 2007

User session filter

Capture the User

By default a HTTP session will expire after 30 minutes of inactivity. Although you can change this by the following entry in the web.xml, where the timeout can be set in minutes:

<session-config>
    <session-timeout>30</session-timeout>
</session-config>

In most of the web form based authentication solutions it is designed so that when an user logs in, it's User object will be looked up from the database and put as an attribute in the HTTP session using HttpSession#setAttribute(). When the session expires or has been invalidated (when the user closes and reopens the browser window, for example), then the User object will be garbaged and become unavailable. So the user have to login everytime when he visits the website using the same PC after a relatively short period of inactivity or when he opens a new browser session. This can be annoying after times if the user visits the website (or forum) very frequently.

At some websites you can see an option "Remember me on this computer" at the login form. Such websites put an unique ID in a long-living cookie, and uses this ID to lookup the usersession and the eventual logged in user in the database. This makes the login totally independent of the lifetime of the HTTP session, so that the user can decide himself when to login and logout. Checking for the logged in user in a new session can be done easily using a Filter. This article shows an example of such a Filter.

Back to top

Prepare DTO's

First prepare the DTO's (Data Transfer Objects) for UserSession and User which can be used to hold information about the usersession and the user. You can map those DTO's to the database.

package mymodel;

import java.util.Date;

public class UserSession {

    // Properties ---------------------------------------------------------------------------------

    private String cookieId;
    private User user;
    private Date creationDate;
    private Date lastVisit;
    private int hits;

    // Constructors -------------------------------------------------------------------------------

    /**
     * Default constructor. 
     */
    public UserSession() {
        // Keep it alive.
    }

    /**
     * Construct new usersession with given cookie ID. 
     */
    public UserSession(String cookieId) {
        this.cookieId = cookieId;
        this.creationDate = new Date();
        this.lastVisit = new Date();
    }

    // Getters and setters ------------------------------------------------------------------------

    // Implement default getters and setters here the usual way.

    // Helpers ------------------------------------------------------------------------------------

    /**
     * Add hit (pageview) to the UserSession. Not necessary, but nice for stats.
     */
    public void addHit() {
        this.hits++;
        this.lastVisit = new Date();
    }

    /**
     * A convenience method to check if User is logged in.
     */
    public boolean isLoggedIn() {
        return user != null;
    }

}
package mymodel;

public class User {

    // Properties ---------------------------------------------------------------------------------

    private Long id;
    private String username;
    private String password;
    // Implement other properties here, depending on the requirements.
    // For example: email address, firstname, lastname, homepage, etc.

    // Getters and setters ------------------------------------------------------------------------

    // Implement default getters and setters here the usual way.

}
Back to top

UserSessionFilter

Here is how a UserSessionFilter should look like.

Note: A DAO example can be found here.

package mycontroller;

import java.io.IOException;
import java.util.UUID;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import mydao.DAOException;
import mydao.DAOFactory;
import mydao.UserSessionDAO;
import mymodel.UserSession;
import mymodel.User;

/**
 * The UserSession filter.
 * @author BalusC
 * @link http://balusc.blogspot.com/2007/03/user-session-filter.html
 */
public class UserSessionFilter implements Filter {

    // Constants ----------------------------------------------------------------------------------

    private static final String MANAGED_BEAN_NAME = "userSession";
    private static final String COOKIE_NAME = "UserSessionFilter.cookieId";
    private static final int COOKIE_MAX_AGE = 31536000; // 60*60*24*365 seconds; 1 year.

    // Vars ---------------------------------------------------------------------------------------

    private UserSessionDAO userSessionDAO;

    // Actions ------------------------------------------------------------------------------------

    /**
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    public void init(FilterConfig filterConfig) {
        // Just do your DAO thing. You can make the databaseName a context init-param as well.
        // Also see the DAO tutorial.
        DAOFactory daoFactory = DAOFactory.getInstance("databaseName");
        userSessionDAO = daoFactory.getUserSessionDAO();
    }

    /**
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, 
     *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException
    {
        // Check PathInfo.
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String pathInfo = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
        if (pathInfo.startsWith("/inc")) {
            // This is not necessary, but it might be useful if you want to skip include
            // files for example. You can put include files (subviews, images, css, js)
            // in one folder, called "/inc". If those include files are loaded, then
            // continue the filter chain and abort this filter, because it is usually not
            // necessary to lookup for any UserSession then. Or, if the url-pattern in the
            // web.xml is specific enough, then this if-block can just be removed.
            chain.doFilter(request, response);
            return;
        }

        // Get UserSession from HttpSession.
        HttpSession httpSession = httpRequest.getSession();
        UserSession userSession = (UserSession) httpSession.getAttribute(MANAGED_BEAN_NAME);

        if (userSession == null) {

            // No UserSession found in HttpSession; lookup ID in cookie.
            String cookieId = getCookieValue(httpRequest, COOKIE_NAME);

            if (cookieId != null) {

                // ID found in cookie. Lookup UserSession by cookie ID in database.
                // Do your "SELECT * FROM UserSession WHERE CookieID" thing.
                try {
                    userSession = userSessionDAO.find(cookieId); 
                    // This can be null. If this is null, then the session is deleted
                    // from DB meanwhile or the cookie is just fake (hackers!).
                } catch (DAOException e) {
                    // Do your exception handling thing.
                    setErrorMessage("Loading UserSession failed.", e);
                }
            }

            if (userSession == null) {

                // No ID found in cookie, or no UserSession found in DB.
                // Create new UserSession.
                // Do your "INSERT INTO UserSession VALUES values" thing.
                cookieId = UUID.randomUUID().toString();
                userSession = new UserSession(cookieId);
                try {
                    userSessionDAO.save(userSession);
                } catch (DAOException e) {
                    // Do your exception handling thing.
                    setErrorMessage("Creating UserSession failed.", e);
                }

                // Put ID in cookie.
                HttpServletResponse httpResponse = (HttpServletResponse) response;
                setCookieValue(httpResponse, COOKIE_NAME, cookieId, COOKIE_MAX_AGE);
            }

            // Set UserSession in current HttpSession.
            httpSession.setAttribute(MANAGED_BEAN_NAME, userSession);
        }

        // Add hit and update UserSession.
        // Do your "UPDATE UserSession SET values WHERE CookieID" thing.
        userSession.addHit();
        try {
            userSessionDAO.save(userSession);
        } catch (DAOException e) {
            // UserSession might be deleted from DB meanwhile.
            // Reset current UserSession and re-filter.
            httpSession.setAttribute(MANAGED_BEAN_NAME, null);
            doFilter(request, response, chain);
            return;
        }

        // Continue filtering.
        chain.doFilter(request, response);
    }

    /**
     * @see javax.servlet.Filter#destroy()
     */
    public void destroy() {
        // Apparently there's nothing to destroy?
    }

    // Helpers (may be refactored to some utility class) ------------------------------------------

    /**
     * Retrieve the cookie value from the given servlet request based on the given
     * cookie name.
     * @param request The HttpServletRequest to be used.
     * @param name The cookie name to retrieve the value for.
     * @return The cookie value associated with the given cookie name.
     */

    public static String getCookieValue(HttpServletRequest request, String name) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie != null && name.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    /**
     * Set the cookie value in the given servlet response based on the given cookie
     * name and expiration interval.
     * @param response The HttpServletResponse to be used.
     * @param name The cookie name to associate the cookie value with.
     * @param value The actual cookie value to be set in the given servlet response.
     * @param maxAge The expiration interval in seconds. If this is set to 0,
     * then the cookie will immediately expire.
     */
    public static void setCookieValue(
        HttpServletResponse response, String name, String value, int maxAge)
    {
        Cookie cookie = new Cookie(name, value);
        cookie.setMaxAge(maxAge);
        response.addCookie(cookie);
    }

}

You can activate the UserSessionFilter by adding the following lines to the web.xml. Take note: the filters are executed in the order as they are definied in the web.xml.

<filter>
    <filter-name>userSessionFilter</filter-name>
    <filter-class>mycontroller.UserSessionFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>userSessionFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
Back to top

Login and Logout

Now, with such an UserSessionFilter the user can decide himself when to login and logout. Here is an example how you can let the user login and logout. It is just all about putting and removing the User object from the UserSession object.

The basic JSF code for the login:

<h:form>
    <h:panelGrid columns="2">
        <h:outputText value="Username" />
        <h:inputText value="#{userForm.username}" />

        <h:outputText value="Password" />
        <h:inputSecret value="#{userForm.password}" />

        <h:panelGroup />
        <h:commandButton value="login" action="#{userForm.login}" />
    </h:panelGrid>
</h:form>

And the basic JSF code for the logout:

<h:form>
    <h:commandButton value="logout" action="#{userForm.logout}" />
</h:form>

Finally the backing bean's code. Note: MathUtil#hashMD5() is described here. A DAO example can be found here.

package mycontroller;

import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;

import mydao.DAOException;
import mydao.DAOFactory;
import mydao.UserDAO;
import mydao.UserSessionDAO;
import mymodel.UserSession;
import mymodel.User;

public class UserForm {

    // Vars ---------------------------------------------------------------------------------------

    // Just do your DAO thing. You can make the databaseName a context init-param as well.
    // Also see the DAO tutorial.
    private static DAOFactory daoFactory = DAOFactory.getInstance("databaseName");
    private static UserDAO userDAO = daoFactory.getUserDAO();
    private static UserSessionDAO userSessionDAO = daoFactory.getUserSessionDAO();

    // Properties --------------------------------------------------------------------------------

    private UserSession userSession;
    private String username;
    private String password;

    // Actions -----------------------------------------------------------------------------------

    public void login() {
        try {

            // Do your "SELECT * FROM User WHERE username AND password" thing.
            User user = userDAO.find(username, password);

            if (user != null) {

                // User found. Put the User in the UserSession.
                userSession.setUser(user);

                // Do your "UPDATE UserSession SET values WHERE CookieID" thing.
                try {
                    userSessionDAO.save(userSession);

                    // Do your succes handling thing.
                    setSuccesMessage("You are logged in successfully!");
                } catch (DAOException e) {
                    // Do your exception handling thing.
                    setErrorMessage("Updating UserSession failed.", e);
                }
            } else {
                // Do your error handling thing.
                setErrorMessage("Unknown username and/or invalid password.");
            }
        } catch (DAOException e) {
            // Do your exception handling thing.
            setErrorMessage("Loading User failed.", e);
        }
    }

    public void logout() {

        // Just null out the user.
        userSession.setUser(null);

        try {
            // Do your "UPDATE UserSession SET values WHERE CookieID" thing.
            userSessionDAO.save(userSession);

            // Do your succes handling thing.
            setSuccesMessage("You are logged out successfully!");
        } catch (DAOException e) {
            // Do your exception handling thing.
            setErrorMessage("Updating UserSession failed.", e);
        }
    }

    // Getters and setters ------------------------------------------------------------------------

    // Implement default getters and setters here the usual way.

}

Declare the UserSession as a session scoped managed bean in the faces-config.xml and declare the UserForm as a request scoped bean with the UserSession instance as a managed property.

<managed-bean>
    <managed-bean-name>userSession</managed-bean-name>
    <managed-bean-class>mymodel.UserSession</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
    <managed-bean-name>userForm</managed-bean-name>
    <managed-bean-class>mycontroller.UserForm</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
        <property-name>userSession</property-name>
        <value>#{userSession}</value>
    </managed-property>
</managed-bean>

The UserSession#isLoggedIn() method can be very useful for JSF pages. You can use it in the rendered attribute of any component for example.

<h:panelGroup rendered="#{!userSession.loggedIn}">
    <h:outputText value="You are not logged in." />
    <%-- put the login code here --%>
</h:panelGroup>

<h:panelGroup rendered="#{userSession.loggedIn}">
    <h:outputText value="You are logged in as #{userSession.user.username}." />
    <%-- put the logout code here --%>
</h:panelGroup>
Back to top

Security considerations

Be aware that the cookie ID ought to be unique for every usersession. In the above example the cookie ID is a 128-bit autogenerated string obtained from java.util.UUID. Although this is extremely hard to wildguess/bruteforce, it may be a good practice to retrieve the IP address from the user using HttpServletRequest#getRemoteAddr() as well and put it along the cookie ID in the database (thus not as value in the cookie itself!). When looking up the usersession in the database you can use SELECT * FROM UserSession WHERE CookieID AND RemoteAddr. One big con is that this does not work very well when the user has a dynamic IP address. But it would be nice if such an option will be provided in the login page: "Lock this login to my IP address" or something.

Last note: the "official" container managed session ID as is in HttpSession#getId() is not alltime-unique. It is only unique inside the running webcontainer between all non-expired HttpSessions. So do not ever consider to use it as session ID for in the cookie!

Back to top

Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.

(C) March 2007, BalusC

24 comments:

mySweetHome said...

sorry for stupid question, but i'm newbie, cannt understand what is "do your DAO", complete package will useful for me...I'm now searching user session management. Can you help me !, thanks.

BalusC said...

Also read the code comments to understand what you should be doing. For example: "// Do your "SELECT * FROM UserSession WHERE SessionID" thing.". Learn JDBC and SQL and write a DAO (Data Access Object) class which does the task. In this example it doesn't matter how you are going to do it, as long as it returns the UserSession object matching the SessionID.

mySweetHome said...

Ohh, i understood what DAO is mean :D, sorry for my bad java.
Thanks balusc!

Raj said...

Hi BalusC,
I watch your code where you did it using DAO. Can i do it using MySQL???? and i want to code it in JSP... Could i use the same bean class that to use it in JSP..

Thanks in advance

P.B.Rajkumar

BalusC said...

This kind of code doesn't belong in JSP. You can just write SQL code in the DAO class and this can be done for any database server who has a JDBC driver available. See my previous comment for the links.

Rajkumar P B said...

Hi,
i use the same code and use JSP as front, but it doesn't works fine for me.. Can u help me?

Beginner said...

Hi, I have read your User session filter post, If I don't implement the cookies (Remember Me in this computer), Can it still done in this way? I only want the user logout or close the browser or in certain period let say 30 min then destroy the session.
And stored the session id inside the database will this more secure? I dont really understand why store the session id inside the database, because i thought that everytime the user login will generate a new session id? correct me if i wrong.

BalusC said...

If you don't need the "Remember me" thing, then this whole article is in fact irrelevant for you.

In your case on login you just need to put the User object in HttpSession. On logout just remove the User object from HttpSession. The session timeout on its turn can be configured in web.xml.

Beginner said...

Ohhh thanks, i understand already

Beginner said...

Hi BalusC, i got a problem even if the user have login the userSession wont give me null value but it return me something like this bean.User@1777b1.

BalusC said...

You need to cast the Object back to User. And when you want to get something meaningful when you do System.out.println(user), then you need to override toString() method.

Jalead said...

Thanks for nice Article.

I wonder if it is possible to integrate this "session filter" in an application that relies on container-managed security like Tomcat 6.

If its not possible, as (per my info) j_security_check login page couldn't be intercepted by filters.

What could be other ideal solution to manage security inside a web- app and still use this filter.

Tatuinf said...

Hi! Great example. Was very useful to me. I'm having some problems on my JSF application in the Filter, maybe you can helpme a little. I have a filter similar to yours, but when I use it, the getRequestURI() method always have the source URI, not the one where I'm trying to go. So I can't validate. Do you know what could be happening? Thanks!

Tatuinf said...

Hi! Great example. Was very useful to me. I'm having some problems on my JSF application in the Filter, maybe you can helpme a little. I have a filter similar to yours, but when I use it, the getRequestURI() method always have the source URI, not the one where I'm trying to go. So I can't validate. Do you know what could be happening? Thanks!

solidforms said...

Hi Mr BalusC i followed your tutorial and everything is woriking perfectly. However i have two problems.
1st:
If i call in rendered a isLogged as boolean it gives me a ServletException: Property not found for type... Can you help me solving this?
2nd:
how do you handle the back button of the browser? i have the request bean and the session bean, but after login if i go back it doesnt expires, shows the login page again :x. Any help?

Regards and thank you.

BalusC said...

@Solidforms: 1) that was a typo in the code. It should be `#{userSession.loggedIn}`. 2) disable browser caching by setting the response headers accordingly. You can find here an overview: http://stackoverflow.com/questions/49547/making-sure-a-web-page-is-not-cached-across-all-browsers/2068407#2068407

solidforms said...

I'm not using it in a panelGroup, instead i use the #{sessionBean.isLoggedIn} in f:view. But i tried in h:form. I'll try to use it in panelGroup to see if he allows me to call a non-string function. But i saw in JSF API that h:panelGroup also has java.lang.String as type for rendered so im hopeless lol.

Regarding to the cache, i tried that also, but the browser seems to ignore it. It uses a regular cache and history is stored differently (i guess). After i manage to put rendered working ill see if the browser calls the server.

Thank you.

david said...

Hi.

I would like to know how to face the next problem. The user close the browser, so all the logic is skipped.

For example: in a bank application the web page works exactly the same when the user logout using the button or closing the browser.

Any idea to control this stuff???

Thanks

Tobi said...

Hey Balusc!

Thx for your great Tutorial and all your shared JSF-Knowledge ;-)

Is this still an up to date way to do User Authentification and Security or would you recommend another way in our year 2010 with jsf-2.0?

Sam said...

Hi, firstly thankyou for all the tutorials on here, they are very useful. I have a question regarding the DTO for a usersession. The dto contains Dates & User objects, what is the best way to represent these in a MySQL table ? Implementing serializeable seems like a over complicated way of doing things and so I feel I am missing the easy answer.

BalusC said...

@Sam: use DATE or DATETIME type for dates or date+time and create a new table for User which you reference as FK on usersession table.

SyamalRao Kasturi said...

Hi..
How to avoid externally created session identifiers in jsf 2.0. I'm getting "Do not accept externally created session identifiers" and "Session Identifier Not Updated" issues during security audit run on my jsf web application. Please help me...

Karl Florberger said...
This comment has been removed by the author.
cerebus said...

Hi

It's an old post but hopefully you're still answering questions.

I'm trying to understand how it's meant to work in general and if I've understood correctly the goal of the filter is to get/create and save the userSession to the HttpSession if it doesn't exist or else just continue.

This will happen for each and every request so even before logging in, you will have a userSession in HttpSession.

In the login() method the user is looked up in the database and added to a new userSession object. As there already is a userSession that's been saved to the database, shouldn't I try to find that one instead of creating a new?

Best regards, Karl