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    }