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 }