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 LOGGER = 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          //more secure, protects against XSS attacks
59          cookie.setHttpOnly(true);
60          this.sessionIdCookie = cookie;
61          this.sessionIdCookieEnabled = true;
62          this.sessionIdUrlRewritingEnabled = false;
63      }
64  
65      public Cookie getSessionIdCookie() {
66          return sessionIdCookie;
67      }
68  
69      @SuppressWarnings({"UnusedDeclaration"})
70      public void setSessionIdCookie(Cookie sessionIdCookie) {
71          this.sessionIdCookie = sessionIdCookie;
72      }
73  
74      public boolean isSessionIdCookieEnabled() {
75          return sessionIdCookieEnabled;
76      }
77  
78      @SuppressWarnings({"UnusedDeclaration"})
79      public void setSessionIdCookieEnabled(boolean sessionIdCookieEnabled) {
80          this.sessionIdCookieEnabled = sessionIdCookieEnabled;
81      }
82  
83      public boolean isSessionIdUrlRewritingEnabled() {
84          return sessionIdUrlRewritingEnabled;
85      }
86  
87      @SuppressWarnings({"UnusedDeclaration"})
88      public void setSessionIdUrlRewritingEnabled(boolean sessionIdUrlRewritingEnabled) {
89          this.sessionIdUrlRewritingEnabled = sessionIdUrlRewritingEnabled;
90      }
91  
92      private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
93          if (currentId == null) {
94              String msg = "sessionId cannot be null when persisting for subsequent requests.";
95              throw new IllegalArgumentException(msg);
96          }
97          Cookie template = getSessionIdCookie();
98          Cookie cookie = new SimpleCookie(template);
99          String idString = currentId.toString();
100         cookie.setValue(idString);
101         cookie.saveTo(request, response);
102         LOGGER.trace("Set session ID cookie for session with id {}", idString);
103     }
104 
105     private void removeSessionIdCookie(HttpServletRequest request, HttpServletResponse response) {
106         getSessionIdCookie().removeFrom(request, response);
107     }
108 
109     private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
110         if (!isSessionIdCookieEnabled()) {
111             LOGGER.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
112             return null;
113         }
114         if (!(request instanceof HttpServletRequest)) {
115             LOGGER.debug("Current request is not an HttpServletRequest - cannot get session ID cookie.  Returning null.");
116             return null;
117         }
118         HttpServletRequest httpRequest = (HttpServletRequest) request;
119         return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
120     }
121 
122     private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
123 
124         String id = getSessionIdCookieValue(request, response);
125         if (id != null) {
126             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
127                     ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
128         } else {
129             //not in a cookie, or cookie is disabled - try the request URI as a fallback (i.e. due to URL rewriting):
130 
131             //try the URI path segment parameters first:
132             id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
133 
134             if (id == null && request instanceof HttpServletRequest) {
135                 //not a URI path segment parameter, try the query parameters:
136                 String name = getSessionIdName();
137                 HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
138                 String queryString = httpServletRequest.getQueryString();
139                 if (queryString != null && queryString.contains(name)) {
140                     id = request.getParameter(name);
141                 }
142                 if (id == null && queryString != null && queryString.contains(name.toLowerCase())) {
143                     //try lowercase:
144                     id = request.getParameter(name.toLowerCase());
145                 }
146             }
147             if (id != null) {
148                 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
149                         ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
150             }
151         }
152         if (id != null) {
153             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
154             //automatically mark it valid here.  If it is invalid, the
155             //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
156             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
157         }
158 
159         // always set rewrite flag - SHIRO-361
160         request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
161 
162         return id;
163     }
164 
165     //SHIRO-351
166     //also see http://cdivilly.wordpress.com/2011/04/22/java-servlets-uri-parameters/
167     //since 1.2.2
168     private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) {
169 
170         if (!(servletRequest instanceof HttpServletRequest)) {
171             return null;
172         }
173         HttpServletRequest request = (HttpServletRequest) servletRequest;
174         String uri = request.getRequestURI();
175         if (uri == null) {
176             return null;
177         }
178 
179         int queryStartIndex = uri.indexOf('?');
180         //get rid of the query string
181         if (queryStartIndex >= 0) {
182             uri = uri.substring(0, queryStartIndex);
183         }
184 
185         //now check for path segment parameters:
186         int index = uri.indexOf(';');
187         if (index < 0) {
188             //no path segment params - return:
189             return null;
190         }
191 
192         //there are path segment params, let's get the last one that may exist:
193         final String token = paramName + "=";
194 
195         //uri now contains only the path segment params
196         uri = uri.substring(index + 1);
197 
198         //we only care about the last JSESSIONID param:
199         index = uri.lastIndexOf(token);
200         if (index < 0) {
201             //no segment param:
202             return null;
203         }
204 
205         uri = uri.substring(index + token.length());
206 
207         //strip off any remaining segment params:
208         index = uri.indexOf(';');
209         if (index >= 0) {
210             uri = uri.substring(0, index);
211         }
212 
213         //what remains is the value
214         return uri;
215     }
216 
217     //since 1.2.1
218     private String getSessionIdName() {
219         String name = this.sessionIdCookie != null ? this.sessionIdCookie.getName() : null;
220         if (name == null) {
221             name = ShiroHttpSession.DEFAULT_SESSION_ID_NAME;
222         }
223         return name;
224     }
225 
226     protected Session createExposedSession(Session session, SessionContext context) {
227         if (!WebUtils.isWeb(context)) {
228             return super.createExposedSession(session, context);
229         }
230         ServletRequest request = WebUtils.getRequest(context);
231         ServletResponse response = WebUtils.getResponse(context);
232         SessionKey key = new WebSessionKey(session.getId(), request, response);
233         return new DelegatingSession(this, key);
234     }
235 
236     protected Session createExposedSession(Session session, SessionKey key) {
237         if (!WebUtils.isWeb(key)) {
238             return super.createExposedSession(session, key);
239         }
240 
241         ServletRequest request = WebUtils.getRequest(key);
242         ServletResponse response = WebUtils.getResponse(key);
243         SessionKey sessionKey = new WebSessionKey(session.getId(), request, response);
244         return new DelegatingSession(this, sessionKey);
245     }
246 
247     /**
248      * Stores the Session's ID, usually as a Cookie, to associate with future requests.
249      *
250      * @param session the session that was just {@link #createSession created}.
251      */
252     @Override
253     protected void onStart(Session session, SessionContext context) {
254         super.onStart(session, context);
255 
256         if (!WebUtils.isHttp(context)) {
257             LOGGER.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response "
258                     + "pair. No session ID cookie will be set.");
259             return;
260 
261         }
262         HttpServletRequest request = WebUtils.getHttpRequest(context);
263         HttpServletResponse response = WebUtils.getHttpResponse(context);
264 
265         if (isSessionIdCookieEnabled()) {
266             Serializable sessionId = session.getId();
267             storeSessionId(sessionId, request, response);
268         } else {
269             LOGGER.debug("Session ID cookie is disabled.  No cookie has been set for new session with id {}", session.getId());
270         }
271 
272         request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
273         request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
274     }
275 
276     @Override
277     public Serializable getSessionId(SessionKey key) {
278         Serializable id = super.getSessionId(key);
279         if (id == null && WebUtils.isWeb(key)) {
280             ServletRequest request = WebUtils.getRequest(key);
281             ServletResponse response = WebUtils.getResponse(key);
282             id = getSessionId(request, response);
283         }
284         return id;
285     }
286 
287     protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
288         return getReferencedSessionId(request, response);
289     }
290 
291     @Override
292     protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
293         super.onExpiration(s, ese, key);
294         onInvalidation(key);
295     }
296 
297     @Override
298     protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) {
299         super.onInvalidation(session, ise, key);
300         onInvalidation(key);
301     }
302 
303     private void onInvalidation(SessionKey key) {
304         ServletRequest request = WebUtils.getRequest(key);
305         if (request != null) {
306             request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID);
307         }
308         if (WebUtils.isHttp(key)) {
309             LOGGER.debug("Referenced session was invalid.  Removing session ID cookie.");
310             removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key));
311         } else {
312             LOGGER.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response "
313                     + "pair. Session ID cookie will not be removed due to invalidated session.");
314         }
315     }
316 
317     @Override
318     protected void onStop(Session session, SessionKey key) {
319         super.onStop(session, key);
320         if (WebUtils.isHttp(key)) {
321             HttpServletRequest request = WebUtils.getHttpRequest(key);
322             HttpServletResponse response = WebUtils.getHttpResponse(key);
323             LOGGER.debug("Session has been stopped (subject logout or explicit stop).  Removing session ID cookie.");
324             removeSessionIdCookie(request, response);
325         } else {
326             LOGGER.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response "
327                     + "pair. Session ID cookie will not be removed due to stopped session.");
328         }
329     }
330 
331     /**
332      * This is a native session manager implementation, so this method returns {@code false} always.
333      *
334      * @return {@code false} always
335      * @since 1.2
336      */
337     public boolean isServletContainerSessions() {
338         return false;
339     }
340 }