Deprecated: Assigning the return value of new by reference is deprecated in /home/emperor/public_html/wiki/inc/parserutils.php on line 202

Deprecated: Assigning the return value of new by reference is deprecated in /home/emperor/public_html/wiki/inc/parserutils.php on line 205

Deprecated: Assigning the return value of new by reference is deprecated in /home/emperor/public_html/wiki/inc/parserutils.php on line 314

Deprecated: Assigning the return value of new by reference is deprecated in /home/emperor/public_html/wiki/inc/parserutils.php on line 454

Strict Standards: Declaration of cache_instructions::retrieveCache() should be compatible with cache::retrieveCache($clean = true) in /home/emperor/public_html/wiki/inc/cache.php on line 291

Deprecated: Function split() is deprecated in /home/emperor/public_html/wiki/inc/auth.php on line 146

Warning: Cannot modify header information - headers already sent by (output started at /home/emperor/public_html/wiki/inc/parserutils.php:202) in /home/emperor/public_html/wiki/inc/auth.php on line 236

Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /home/emperor/public_html/wiki/inc/auth.php on line 390

Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /home/emperor/public_html/wiki/inc/auth.php on line 390

Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /home/emperor/public_html/wiki/inc/auth.php on line 387

Strict Standards: Only variables should be passed by reference in /home/emperor/public_html/wiki/doku.php on line 69

Warning: Cannot modify header information - headers already sent by (output started at /home/emperor/public_html/wiki/inc/parserutils.php:202) in /home/emperor/public_html/wiki/inc/actions.php on line 124
snippets:sessiontrackingfilter [MojaveWiki]
 

SessionTrackingFilter

Please see blog entry: http://www.mojavelinux.com/blog/archives/2006/09/improved_session_tracking/ for explanation.

import java.io.IOException;
import java.util.Date;
 
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 org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
 
/**
 * <p>Tracks the user agent and validates the session originator.</p>
 * 
 * <p>The {@link HttpSession} is validated by comparing a token that is placed in the session upon creation against
 * a permanent cookie that acts as a unique user-agent (browser) identifier.  If the token does not match, then
 * it means that a URL containing the session id has migrated from one user-agent to another, which is not permitted
 * for security reasons.  In that case, an error is displayed notifying the user that this operation is
 * not allowed and requiring the user to login again.</p>
 * <p>If the session is new, then the user-agent is assigned an identifier (if it hasn't already been assigned one) and
 * stores that identifier as a token in the session, marking this user-agent as the session originator.</p>
 * 
 * @web.filter
 *   name="SessionTrackingFilter"
 *   description="Tracks the user agent and validates the session originator"
 * @web.filter-mapping
 *   url-pattern="*.jsf"
 *   dispatcher="REQUEST"
 * 
 * @author Dan Allen <dallen@coderyte.com>
 */
public class SessionTrackingFilter implements Filter {
 
	private static Log logger = LogFactory.getLog( SessionTrackingFilter.class );
	
	private static final String FILTER_APPLIED = "__session_tracking_filter_applied";
 
	private static final String USER_AGENT_ID_COOKIE_NAME = "UAID";
	
	private static final String SESSION_ORIGINATOR_ATTR = "__session_originator";
	
	public void init( FilterConfig config ) throws ServletException {
	}
 
	public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain ) throws IOException,
		ServletException {
 
		if ( servletRequest.getAttribute( FILTER_APPLIED ) != null ) {
			chain.doFilter( servletRequest, servletResponse );
			return;
		}
		
		if ( !(servletRequest instanceof HttpServletRequest ) || !(servletResponse instanceof HttpServletResponse) ) {
			chain.doFilter( servletRequest, servletResponse );
			return;
		}
		
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		
		if ( request.getAttribute( FILTER_APPLIED ) == null ) {
			
			// see if the browser uid is the same as the session originator; if not, deny access
			if ( request.getSession( false ) != null && !request.getSession().isNew() && !isSessionOriginator( request ) ) {
				if ( logger.isDebugEnabled() ) {
					logger.debug( "Preventing access to session since user agent is not the originator" );
				}
				
				// QUESTION: should recontructing the url be done in a utility instead?
				StringBuffer url = request.getRequestURL();
				
				// strip off the session id part if it came with requestURL (jetty does this)
				if (url.indexOf( ";jsessionid=" ) != -1) {
					url.delete( url.indexOf( ";jsessionid=" ), url.length() );
				}
				
				if ( !StringUtils.isEmpty( request.getQueryString() ) ) {
					url.append( "?" );
					url.append( request.getQueryString() );
				}
				
				response.sendRedirect( url.toString() );
				
				// QUESTION: should we instead send either 203 (bad meta), 400 (bad request) or 412 (precondition)
				//response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "The URL you requested is attempting to use a session that was originated by another computer (or browser).  You will be required to login again." );
				return;
			}
			
			// create a new session if one does not exist or if it is still unclaimed
			if ( request.getSession( true ).isNew() || request.getSession().getAttribute( SESSION_ORIGINATOR_ATTR ) == null ) {
				// take ownership over this session
				bindToSession( request, response );
			}
 
			request.setAttribute( FILTER_APPLIED, Boolean.TRUE );
		}
		
		chain.doFilter( servletRequest, servletResponse );
	}
 
	private boolean isSessionOriginator( HttpServletRequest request ) {
		// check if session is still unclaimed (created somewhere on the way to this filter)
		if ( request.getSession().getAttribute( SESSION_ORIGINATOR_ATTR ) == null ) {
			return true;
		}
		
		String userAgentId = getUserAgentId( request );
		return ( userAgentId != null && userAgentId.equals( request.getSession().getAttribute( SESSION_ORIGINATOR_ATTR ) ) );
	}
	
	private void bindToSession( HttpServletRequest request, HttpServletResponse response ) {
		String userAgentId = getUserAgentId( request );
		if ( StringUtils.isEmpty( userAgentId ) ) {
			userAgentId = generateUserAgentId( request );
			Cookie c = new Cookie( USER_AGENT_ID_COOKIE_NAME, userAgentId );
			// 5 years...should be plenty
			c.setMaxAge( 60 * 60 * 24 * 365 * 5 );
			c.setPath( "/" );
			c.setSecure( request.isSecure() );
			response.addCookie( c );
		}
		
		request.getSession().setAttribute( SESSION_ORIGINATOR_ATTR, userAgentId );
	}
	
	private String getUserAgentId( HttpServletRequest request ) {
		if ( request.getCookies() == null ) {
			return null;
		}
		
		for ( Cookie cookie : request.getCookies() ) {
			if ( cookie.getName().equals( USER_AGENT_ID_COOKIE_NAME ) ) {
				return cookie.getValue();
			}
		}
		
		return null;
	}
	
	/**
	 * Using the message digest algorithm, generate a unique identifier for this browser,
	 * using the server domain name, a random number and the current time in milliseconds
	 * as the salts.
	 */
	private String generateUserAgentId( HttpServletRequest request ) {
		StringBuffer userAgentId = new StringBuffer();
		userAgentId.append( DigestUtils.md5Hex( request.getRemoteAddr() ) );
		userAgentId.append( "." );
		userAgentId.append( DigestUtils.md5Hex( String.valueOf( Math.round( Math.random() * 2147483647 ) ) ) );
		userAgentId.append( "." );
		userAgentId.append( DigestUtils.md5Hex( String.valueOf( Math.round( new Date().getTime() / 1000 ) ) ) );
		return userAgentId.toString();
	}
	
	public void destroy() {
	}
	
}
 
snippets/sessiontrackingfilter.txt · Last modified: 2007/09/14 08:19 by dallen
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki

Strict Standards: Only variables should be passed by reference in /home/emperor/public_html/wiki/doku.php on line 77