001 package hirondelle.web4j.security; 002 003 import java.io.*; 004 import java.util.logging.*; 005 import javax.servlet.Filter; 006 import javax.servlet.FilterConfig; 007 import javax.servlet.ServletException; 008 import javax.servlet.FilterChain; 009 import javax.servlet.ServletRequest; 010 import javax.servlet.ServletResponse; 011 import javax.servlet.http.HttpServletRequest; 012 import javax.servlet.http.HttpServletRequestWrapper; 013 import javax.servlet.http.HttpServletResponse; 014 import javax.servlet.http.HttpServletResponseWrapper; 015 import javax.servlet.http.HttpSession; 016 import hirondelle.web4j.util.Util; 017 018 /** 019 Suppress the creation of unwanted sessions. 020 021 <P><b>Using this filter means that browsers must have cookies enabled.</b> 022 Some users believe that disabling cookies protects them. For web applications, 023 this seems unadvisable since its replacement -- URL rewriting -- has a 024 much higher security risk. URL rewriting is <em>dangerous</em> since it is a vector for 025 session hijacking and session fixation. 026 027 <P>This class can be used only when form-based login is used. 028 When form-based login is used, the generation of the initial <tt>JSESSIONID</tt> 029 cookie is done only once per session, by the container. This filter helps you enforce the 030 policy that form-based login should be the only time a session cookie 031 is generated. 032 033 <P>Superfluous sessions and session ids represent a security risk. 034 Here, the following approach is taken: 035 <ul> 036 <li>during form-based login, the <em>container</em> sends a session cookie to the browser 037 <li>at no other time does the <em>web application</em> itself send a <tt>JSESSIONID</tt>, either in a 038 cookie or in a rewritten URL 039 <li>upon logoff, the <em>web application</em> instructs the browser to delete the <tt>JSESSIONID</tt> cookie 040 </ul> 041 042 <P>Note how the container and the web application work together to manage the <tt>JSESSIONID</tt> cookie. 043 044 <P>It's unfortunate that the Servlet API and Java Server Pages make it 045 a bit too easy to create new sessions. To circumvent that, this filter uses 046 custom wrappers for the underlying HTTP request and response. 047 These wrappers alter the implementations of the following methods related to creating sessions : 048 <ul> 049 <li><tt>{@link javax.servlet.http.HttpServletRequest#getSession()}</tt> 050 <li><tt>{@link javax.servlet.http.HttpServletRequest#getSession(boolean)}</tt> 051 <li><tt>{@link javax.servlet.http.HttpServletResponse#encodeRedirectURL(java.lang.String)}</tt> 052 <li><tt>{@link javax.servlet.http.HttpServletResponse#encodeRedirectURL(java.lang.String)}</tt> 053 <li><tt>{@link javax.servlet.http.HttpServletResponse#encodeRedirectUrl(java.lang.String)}</tt> 054 <li><tt>{@link javax.servlet.http.HttpServletResponse#encodeURL(java.lang.String)}</tt> 055 <li><tt>{@link javax.servlet.http.HttpServletResponse#encodeUrl(java.lang.String)}</tt> 056 </ul> 057 058 <b>Calls to the <tt>getSession</tt> methods are in effect all coerced to <tt>getSession(false)</tt>.</b> 059 <b>Since this doesn't affect the form-based login mechanism, the user will 060 still receive a <tt>JSESSIONID</tt> cookie during form-based login</b>. This policy ensures that your code 061 cannot mistakenly create a superfluous session. 062 063 <P>The <tt>encodeXXX</tt> methods are no-operations, and simply return the given String unchanged. 064 This policy in effect <b>disables URL rewriting</b>. URL rewriting is a security risk since it allows 065 session ids to appear in simple links, which are subject to session hijacking. 066 067 <P>As a convenience, this class will also detect sessions that do not have a user login, 068 and will log such occurrences as a warning. 069 */ 070 public class SuppressUnwantedSessions implements Filter { 071 072 public void doFilter(ServletRequest aRequest, ServletResponse aResponse, FilterChain aChain) throws IOException, ServletException { 073 fLogger.fine("START SuppressUnwantedSessions Filter."); 074 075 HttpServletResponse response = (HttpServletResponse) aResponse; 076 HttpServletRequest request = (HttpServletRequest) aRequest; 077 078 DisableSessionCreation requestWrapper = new DisableSessionCreation(request); 079 DisableUrlRewriting responseWrapper = new DisableUrlRewriting(response); 080 081 aChain.doFilter(requestWrapper, responseWrapper); 082 083 checkForSessionWithNoLogin((HttpServletRequest)aRequest); 084 085 fLogger.fine("END SuppressUnwantedSessions Filter."); 086 } 087 088 /** This implementation does nothing. */ 089 public void init(FilterConfig aFilterConfig) { 090 fLogger.config("INIT : " + this.getClass().getName()); 091 } 092 093 /** This implementation does nothing. */ 094 public void destroy() { 095 fLogger.config("DESTROY : " + this.getClass().getName()); 096 } 097 098 // PRIVATE // 099 private static final Logger fLogger = Util.getLogger(SuppressUnwantedSessions.class); 100 private static final boolean DO_NOT_CREATE = false; 101 102 private void checkForSessionWithNoLogin(HttpServletRequest aRequest) { 103 HttpSession session = aRequest.getSession(DO_NOT_CREATE); 104 if ( sessionAlreadyExists(session) ){ 105 if( ! userHasLoggedIn(aRequest)){ 106 fLogger.warning("Session exists, but user has not yet logged in."); 107 } 108 } 109 } 110 111 private boolean sessionAlreadyExists(HttpSession aSession){ 112 return aSession != null; 113 } 114 115 private boolean userHasLoggedIn(HttpServletRequest aRequest){ 116 return aRequest.getUserPrincipal() != null; 117 } 118 119 /** Alter the implementation of <tt>getSession()</tt> methods. */ 120 private static final class DisableSessionCreation extends HttpServletRequestWrapper { 121 DisableSessionCreation(HttpServletRequest aRequest){ 122 super(aRequest); 123 } 124 @Override public HttpSession getSession() { 125 // FYI - Tomcat 5.5 JSPs call getSession(). 126 fLog.fine("Calling getSession(). Method not supported by this wrapper. Coercing to getSession(false)."); 127 return getSession(false); 128 } 129 @Override public HttpSession getSession(boolean aCreateIfAbsent) { 130 if( aCreateIfAbsent ){ 131 fLog.warning("Calling getSession(true). Method call not supported by this wrapper. Coercing to getSession(false)."); 132 } 133 else { 134 //fLogger.finest("Calling getSession(false)."); 135 } 136 return super.getSession(NEVER_CREATE); 137 } 138 private static final Logger fLog = Util.getLogger(DisableSessionCreation.class); 139 private static final boolean NEVER_CREATE = false; 140 } 141 142 /** Disable URL rewriting entirely. */ 143 private static final class DisableUrlRewriting extends HttpServletResponseWrapper { 144 DisableUrlRewriting(HttpServletResponse aResponse){ 145 super(aResponse); 146 } 147 @Override public String encodeUrl(String aURL) { 148 return aURL; 149 } 150 @Override public String encodeURL(String aURL) { 151 return aURL; 152 } 153 @Override public String encodeRedirectURL(String aURL) { 154 return aURL; 155 } 156 @Override public String encodeRedirectUrl(String aURL) { 157 return aURL; 158 } 159 } 160 }