001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.shiro.web.session.mgt; 020 021import org.apache.shiro.session.ExpiredSessionException; 022import org.apache.shiro.session.InvalidSessionException; 023import org.apache.shiro.session.Session; 024import org.apache.shiro.session.mgt.DefaultSessionManager; 025import org.apache.shiro.session.mgt.DelegatingSession; 026import org.apache.shiro.session.mgt.SessionContext; 027import org.apache.shiro.session.mgt.SessionKey; 028import org.apache.shiro.web.servlet.Cookie; 029import org.apache.shiro.web.servlet.ShiroHttpServletRequest; 030import org.apache.shiro.web.servlet.ShiroHttpSession; 031import org.apache.shiro.web.servlet.SimpleCookie; 032import org.apache.shiro.web.util.WebUtils; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import javax.servlet.ServletRequest; 037import javax.servlet.ServletResponse; 038import javax.servlet.http.HttpServletRequest; 039import javax.servlet.http.HttpServletResponse; 040import java.io.Serializable; 041 042 043/** 044 * Web-application capable {@link org.apache.shiro.session.mgt.SessionManager SessionManager} implementation. 045 * 046 * @since 0.9 047 */ 048public class DefaultWebSessionManager extends DefaultSessionManager implements WebSessionManager { 049 050 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWebSessionManager.class); 051 052 private Cookie sessionIdCookie; 053 private boolean sessionIdCookieEnabled; 054 private boolean sessionIdUrlRewritingEnabled; 055 056 public DefaultWebSessionManager() { 057 Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME); 058 //more secure, protects against XSS attacks 059 cookie.setHttpOnly(true); 060 if (!Boolean.getBoolean(SECURE_COOKIE_DISABLED)) { 061 cookie.setSecure(true); 062 } 063 this.sessionIdCookie = cookie; 064 this.sessionIdCookieEnabled = true; 065 this.sessionIdUrlRewritingEnabled = false; 066 } 067 068 public Cookie getSessionIdCookie() { 069 return sessionIdCookie; 070 } 071 072 @SuppressWarnings({"UnusedDeclaration"}) 073 public void setSessionIdCookie(Cookie sessionIdCookie) { 074 this.sessionIdCookie = sessionIdCookie; 075 } 076 077 public boolean isSessionIdCookieEnabled() { 078 return sessionIdCookieEnabled; 079 } 080 081 @SuppressWarnings({"UnusedDeclaration"}) 082 public void setSessionIdCookieEnabled(boolean sessionIdCookieEnabled) { 083 this.sessionIdCookieEnabled = sessionIdCookieEnabled; 084 } 085 086 public boolean isSessionIdUrlRewritingEnabled() { 087 return sessionIdUrlRewritingEnabled; 088 } 089 090 @SuppressWarnings({"UnusedDeclaration"}) 091 public void setSessionIdUrlRewritingEnabled(boolean sessionIdUrlRewritingEnabled) { 092 this.sessionIdUrlRewritingEnabled = sessionIdUrlRewritingEnabled; 093 } 094 095 private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) { 096 if (currentId == null) { 097 String msg = "sessionId cannot be null when persisting for subsequent requests."; 098 throw new IllegalArgumentException(msg); 099 } 100 Cookie template = getSessionIdCookie(); 101 Cookie cookie = new SimpleCookie(template); 102 String idString = currentId.toString(); 103 cookie.setValue(idString); 104 cookie.saveTo(request, response); 105 LOGGER.trace("Set session ID cookie for session with id {}", idString); 106 } 107 108 private void removeSessionIdCookie(HttpServletRequest request, HttpServletResponse response) { 109 getSessionIdCookie().removeFrom(request, response); 110 } 111 112 private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) { 113 if (!isSessionIdCookieEnabled()) { 114 LOGGER.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie."); 115 return null; 116 } 117 if (!(request instanceof HttpServletRequest)) { 118 LOGGER.debug("Current request is not an HttpServletRequest - cannot get session ID cookie. Returning null."); 119 return null; 120 } 121 HttpServletRequest httpRequest = (HttpServletRequest) request; 122 return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response)); 123 } 124 125 private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) { 126 127 String id = getSessionIdCookieValue(request, response); 128 if (id != null) { 129 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, 130 ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE); 131 } else { 132 //not in a cookie, or cookie is disabled - try the request URI as a fallback (i.e. due to URL rewriting): 133 134 //try the URI path segment parameters first: 135 id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME); 136 137 if (id == null && request instanceof HttpServletRequest) { 138 //not a URI path segment parameter, try the query parameters: 139 String name = getSessionIdName(); 140 HttpServletRequest httpServletRequest = WebUtils.toHttp(request); 141 String queryString = httpServletRequest.getQueryString(); 142 if (queryString != null && queryString.contains(name)) { 143 id = request.getParameter(name); 144 } 145 if (id == null && queryString != null && queryString.contains(name.toLowerCase())) { 146 //try lowercase: 147 id = request.getParameter(name.toLowerCase()); 148 } 149 } 150 if (id != null) { 151 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, 152 ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); 153 } 154 } 155 if (id != null) { 156 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); 157 //automatically mark it valid here. If it is invalid, the 158 //onUnknownSession method below will be invoked and we'll remove the attribute at that time. 159 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); 160 } 161 162 // always set rewrite flag - SHIRO-361 163 request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled()); 164 165 return id; 166 } 167 168 //SHIRO-351 169 //also see http://cdivilly.wordpress.com/2011/04/22/java-servlets-uri-parameters/ 170 //since 1.2.2 171 private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) { 172 173 if (!(servletRequest instanceof HttpServletRequest)) { 174 return null; 175 } 176 HttpServletRequest request = (HttpServletRequest) servletRequest; 177 String uri = request.getRequestURI(); 178 if (uri == null) { 179 return null; 180 } 181 182 int queryStartIndex = uri.indexOf('?'); 183 //get rid of the query string 184 if (queryStartIndex >= 0) { 185 uri = uri.substring(0, queryStartIndex); 186 } 187 188 //now check for path segment parameters: 189 int index = uri.indexOf(';'); 190 if (index < 0) { 191 //no path segment params - return: 192 return null; 193 } 194 195 //there are path segment params, let's get the last one that may exist: 196 final String token = paramName + "="; 197 198 //uri now contains only the path segment params 199 uri = uri.substring(index + 1); 200 201 //we only care about the last JSESSIONID param: 202 index = uri.lastIndexOf(token); 203 if (index < 0) { 204 //no segment param: 205 return null; 206 } 207 208 uri = uri.substring(index + token.length()); 209 210 //strip off any remaining segment params: 211 index = uri.indexOf(';'); 212 if (index >= 0) { 213 uri = uri.substring(0, index); 214 } 215 216 //what remains is the value 217 return uri; 218 } 219 220 //since 1.2.1 221 private String getSessionIdName() { 222 String name = this.sessionIdCookie != null ? this.sessionIdCookie.getName() : null; 223 if (name == null) { 224 name = ShiroHttpSession.DEFAULT_SESSION_ID_NAME; 225 } 226 return name; 227 } 228 229 protected Session createExposedSession(Session session, SessionContext context) { 230 if (!WebUtils.isWeb(context)) { 231 return super.createExposedSession(session, context); 232 } 233 ServletRequest request = WebUtils.getRequest(context); 234 ServletResponse response = WebUtils.getResponse(context); 235 SessionKey key = new WebSessionKey(session.getId(), request, response); 236 return new DelegatingSession(this, key); 237 } 238 239 protected Session createExposedSession(Session session, SessionKey key) { 240 if (!WebUtils.isWeb(key)) { 241 return super.createExposedSession(session, key); 242 } 243 244 ServletRequest request = WebUtils.getRequest(key); 245 ServletResponse response = WebUtils.getResponse(key); 246 SessionKey sessionKey = new WebSessionKey(session.getId(), request, response); 247 return new DelegatingSession(this, sessionKey); 248 } 249 250 /** 251 * Stores the Session's ID, usually as a Cookie, to associate with future requests. 252 * 253 * @param session the session that was just {@link #createSession created}. 254 */ 255 @Override 256 protected void onStart(Session session, SessionContext context) { 257 super.onStart(session, context); 258 259 if (!WebUtils.isHttp(context)) { 260 LOGGER.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " 261 + "pair. No session ID cookie will be set."); 262 return; 263 264 } 265 HttpServletRequest request = WebUtils.getHttpRequest(context); 266 HttpServletResponse response = WebUtils.getHttpResponse(context); 267 268 if (isSessionIdCookieEnabled()) { 269 Serializable sessionId = session.getId(); 270 storeSessionId(sessionId, request, response); 271 } else { 272 LOGGER.debug("Session ID cookie is disabled. No cookie has been set for new session with id {}", session.getId()); 273 } 274 275 request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE); 276 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE); 277 } 278 279 @Override 280 public Serializable getSessionId(SessionKey key) { 281 Serializable id = super.getSessionId(key); 282 if (id == null && WebUtils.isWeb(key)) { 283 ServletRequest request = WebUtils.getRequest(key); 284 ServletResponse response = WebUtils.getResponse(key); 285 id = getSessionId(request, response); 286 } 287 return id; 288 } 289 290 protected Serializable getSessionId(ServletRequest request, ServletResponse response) { 291 return getReferencedSessionId(request, response); 292 } 293 294 @Override 295 protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) { 296 super.onExpiration(s, ese, key); 297 onInvalidation(key); 298 } 299 300 @Override 301 protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) { 302 super.onInvalidation(session, ise, key); 303 onInvalidation(key); 304 } 305 306 private void onInvalidation(SessionKey key) { 307 ServletRequest request = WebUtils.getRequest(key); 308 if (request != null) { 309 request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID); 310 } 311 if (WebUtils.isHttp(key)) { 312 LOGGER.debug("Referenced session was invalid. Removing session ID cookie."); 313 removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key)); 314 } else { 315 LOGGER.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " 316 + "pair. Session ID cookie will not be removed due to invalidated session."); 317 } 318 } 319 320 @Override 321 protected void onStop(Session session, SessionKey key) { 322 super.onStop(session, key); 323 if (WebUtils.isHttp(key)) { 324 HttpServletRequest request = WebUtils.getHttpRequest(key); 325 HttpServletResponse response = WebUtils.getHttpResponse(key); 326 LOGGER.debug("Session has been stopped (subject logout or explicit stop). Removing session ID cookie."); 327 removeSessionIdCookie(request, response); 328 } else { 329 LOGGER.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " 330 + "pair. Session ID cookie will not be removed due to stopped session."); 331 } 332 } 333 334 /** 335 * This is a native session manager implementation, so this method returns {@code false} always. 336 * 337 * @return {@code false} always 338 * @since 1.2 339 */ 340 public boolean isServletContainerSessions() { 341 return false; 342 } 343}