View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.web.session.mgt;
20  
21  import org.apache.shiro.session.ExpiredSessionException;
22  import org.apache.shiro.session.InvalidSessionException;
23  import org.apache.shiro.session.Session;
24  import org.apache.shiro.session.mgt.DefaultSessionManager;
25  import org.apache.shiro.session.mgt.DelegatingSession;
26  import org.apache.shiro.session.mgt.SessionContext;
27  import org.apache.shiro.session.mgt.SessionKey;
28  import org.apache.shiro.web.servlet.Cookie;
29  import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
30  import org.apache.shiro.web.servlet.ShiroHttpSession;
31  import org.apache.shiro.web.servlet.SimpleCookie;
32  import org.apache.shiro.web.util.WebUtils;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import javax.servlet.ServletRequest;
37  import javax.servlet.ServletResponse;
38  import javax.servlet.http.HttpServletRequest;
39  import javax.servlet.http.HttpServletResponse;
40  import java.io.Serializable;
41  
42  
43  /**
44   * Web-application capable {@link org.apache.shiro.session.mgt.SessionManager SessionManager} implementation.
45   *
46   * @since 0.9
47   */
48  public class DefaultWebSessionManager extends DefaultSessionManager implements WebSessionManager {
49  
50      private static final Logger log = LoggerFactory.getLogger(DefaultWebSessionManager.class);
51  
52      private Cookie sessionIdCookie;
53      private boolean sessionIdCookieEnabled;
54      private boolean sessionIdUrlRewritingEnabled;
55  
56      public DefaultWebSessionManager() {
57          Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
58          cookie.setHttpOnly(true); //more secure, protects against XSS attacks
59          this.sessionIdCookie = cookie;
60          this.sessionIdCookieEnabled = true;
61          this.sessionIdUrlRewritingEnabled = true;
62      }
63  
64      public Cookie getSessionIdCookie() {
65          return sessionIdCookie;
66      }
67  
68      @SuppressWarnings({"UnusedDeclaration"})
69      public void setSessionIdCookie(Cookie sessionIdCookie) {
70          this.sessionIdCookie = sessionIdCookie;
71      }
72  
73      public boolean isSessionIdCookieEnabled() {
74          return sessionIdCookieEnabled;
75      }
76  
77      @SuppressWarnings({"UnusedDeclaration"})
78      public void setSessionIdCookieEnabled(boolean sessionIdCookieEnabled) {
79          this.sessionIdCookieEnabled = sessionIdCookieEnabled;
80      }
81  
82      public boolean isSessionIdUrlRewritingEnabled() {
83          return sessionIdUrlRewritingEnabled;
84      }
85  
86      @SuppressWarnings({"UnusedDeclaration"})
87      public void setSessionIdUrlRewritingEnabled(boolean sessionIdUrlRewritingEnabled) {
88          this.sessionIdUrlRewritingEnabled = sessionIdUrlRewritingEnabled;
89      }
90  
91      private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
92          if (currentId == null) {
93              String msg = "sessionId cannot be null when persisting for subsequent requests.";
94              throw new IllegalArgumentException(msg);
95          }
96          Cookie template = getSessionIdCookie();
97          Cookie cookie = new SimpleCookie(template);
98          String idString = currentId.toString();
99          cookie.setValue(idString);
100         cookie.saveTo(request, response);
101         log.trace("Set session ID cookie for session with id {}", idString);
102     }
103 
104     private void removeSessionIdCookie(HttpServletRequest request, HttpServletResponse response) {
105         getSessionIdCookie().removeFrom(request, response);
106     }
107 
108     private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
109         if (!isSessionIdCookieEnabled()) {
110             log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
111             return null;
112         }
113         if (!(request instanceof HttpServletRequest)) {
114             log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie.  Returning null.");
115             return null;
116         }
117         HttpServletRequest httpRequest = (HttpServletRequest) request;
118         return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
119     }
120 
121     private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
122 
123         String id = getSessionIdCookieValue(request, response);
124         if (id != null) {
125             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
126                     ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
127         } else {
128             //not in a cookie, or cookie is disabled - try the request URI as a fallback (i.e. due to URL rewriting):
129 
130             //try the URI path segment parameters first:
131             id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
132 
133             if (id == null) {
134                 //not a URI path segment parameter, try the query parameters:
135                 String name = getSessionIdName();
136                 id = request.getParameter(name);
137                 if (id == null) {
138                     //try lowercase:
139                     id = request.getParameter(name.toLowerCase());
140                 }
141             }
142             if (id != null) {
143                 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
144                         ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
145             }
146         }
147         if (id != null) {
148             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
149             //automatically mark it valid here.  If it is invalid, the
150             //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
151             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
152         }
153 
154         // always set rewrite flag - SHIRO-361
155         request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
156 
157         return id;
158     }
159 
160     //SHIRO-351
161     //also see http://cdivilly.wordpress.com/2011/04/22/java-servlets-uri-parameters/
162     //since 1.2.2
163     private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) {
164 
165         if (!(servletRequest instanceof HttpServletRequest)) {
166             return null;
167         }
168         HttpServletRequest request = (HttpServletRequest)servletRequest;
169         String uri = request.getRequestURI();
170         if (uri == null) {
171             return null;
172         }
173 
174         int queryStartIndex = uri.indexOf('?');
175         if (queryStartIndex >= 0) { //get rid of the query string
176             uri = uri.substring(0, queryStartIndex);
177         }
178 
179         int index = uri.indexOf(';'); //now check for path segment parameters:
180         if (index < 0) {
181             //no path segment params - return:
182             return null;
183         }
184 
185         //there are path segment params, let's get the last one that may exist:
186 
187         final String TOKEN = paramName + "=";
188 
189         uri = uri.substring(index+1); //uri now contains only the path segment params
190 
191         //we only care about the last JSESSIONID param:
192         index = uri.lastIndexOf(TOKEN);
193         if (index < 0) {
194             //no segment param:
195             return null;
196         }
197 
198         uri = uri.substring(index + TOKEN.length());
199 
200         index = uri.indexOf(';'); //strip off any remaining segment params:
201         if(index >= 0) {
202             uri = uri.substring(0, index);
203         }
204 
205         return uri; //what remains is the value
206     }
207 
208     //since 1.2.1
209     private String getSessionIdName() {
210         String name = this.sessionIdCookie != null ? this.sessionIdCookie.getName() : null;
211         if (name == null) {
212             name = ShiroHttpSession.DEFAULT_SESSION_ID_NAME;
213         }
214         return name;
215     }
216 
217     protected Session createExposedSession(Session session, SessionContext context) {
218         if (!WebUtils.isWeb(context)) {
219             return super.createExposedSession(session, context);
220         }
221         ServletRequest request = WebUtils.getRequest(context);
222         ServletResponse response = WebUtils.getResponse(context);
223         SessionKey key = new WebSessionKey(session.getId(), request, response);
224         return new DelegatingSession(this, key);
225     }
226 
227     protected Session createExposedSession(Session session, SessionKey key) {
228         if (!WebUtils.isWeb(key)) {
229             return super.createExposedSession(session, key);
230         }
231 
232         ServletRequest request = WebUtils.getRequest(key);
233         ServletResponse response = WebUtils.getResponse(key);
234         SessionKey sessionKey = new WebSessionKey(session.getId(), request, response);
235         return new DelegatingSession(this, sessionKey);
236     }
237 
238     /**
239      * Stores the Session's ID, usually as a Cookie, to associate with future requests.
240      *
241      * @param session the session that was just {@link #createSession created}.
242      */
243     @Override
244     protected void onStart(Session session, SessionContext context) {
245         super.onStart(session, context);
246 
247         if (!WebUtils.isHttp(context)) {
248             log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " +
249                     "pair. No session ID cookie will be set.");
250             return;
251 
252         }
253         HttpServletRequest request = WebUtils.getHttpRequest(context);
254         HttpServletResponse response = WebUtils.getHttpResponse(context);
255 
256         if (isSessionIdCookieEnabled()) {
257             Serializable sessionId = session.getId();
258             storeSessionId(sessionId, request, response);
259         } else {
260             log.debug("Session ID cookie is disabled.  No cookie has been set for new session with id {}", session.getId());
261         }
262 
263         request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
264         request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
265     }
266 
267     @Override
268     public Serializable getSessionId(SessionKey key) {
269         Serializable id = super.getSessionId(key);
270         if (id == null && WebUtils.isWeb(key)) {
271             ServletRequest request = WebUtils.getRequest(key);
272             ServletResponse response = WebUtils.getResponse(key);
273             id = getSessionId(request, response);
274         }
275         return id;
276     }
277 
278     protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
279         return getReferencedSessionId(request, response);
280     }
281 
282     @Override
283     protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
284         super.onExpiration(s, ese, key);
285         onInvalidation(key);
286     }
287 
288     @Override
289     protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) {
290         super.onInvalidation(session, ise, key);
291         onInvalidation(key);
292     }
293 
294     private void onInvalidation(SessionKey key) {
295         ServletRequest request = WebUtils.getRequest(key);
296         if (request != null) {
297             request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID);
298         }
299         if (WebUtils.isHttp(key)) {
300             log.debug("Referenced session was invalid.  Removing session ID cookie.");
301             removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key));
302         } else {
303             log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
304                     "pair. Session ID cookie will not be removed due to invalidated session.");
305         }
306     }
307 
308     @Override
309     protected void onStop(Session session, SessionKey key) {
310         super.onStop(session, key);
311         if (WebUtils.isHttp(key)) {
312             HttpServletRequest request = WebUtils.getHttpRequest(key);
313             HttpServletResponse response = WebUtils.getHttpResponse(key);
314             log.debug("Session has been stopped (subject logout or explicit stop).  Removing session ID cookie.");
315             removeSessionIdCookie(request, response);
316         } else {
317             log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
318                     "pair. Session ID cookie will not be removed due to stopped session.");
319         }
320     }
321 
322     /**
323      * This is a native session manager implementation, so this method returns {@code false} always.
324      *
325      * @return {@code false} always
326      * @since 1.2
327      */
328     public boolean isServletContainerSessions() {
329         return false;
330     }
331 }