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