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 this.sessionIdCookie = cookie; 061 this.sessionIdCookieEnabled = true; 062 this.sessionIdUrlRewritingEnabled = false; 063 } 064 065 public Cookie getSessionIdCookie() { 066 return sessionIdCookie; 067 } 068 069 @SuppressWarnings({"UnusedDeclaration"}) 070 public void setSessionIdCookie(Cookie sessionIdCookie) { 071 this.sessionIdCookie = sessionIdCookie; 072 } 073 074 public boolean isSessionIdCookieEnabled() { 075 return sessionIdCookieEnabled; 076 } 077 078 @SuppressWarnings({"UnusedDeclaration"}) 079 public void setSessionIdCookieEnabled(boolean sessionIdCookieEnabled) { 080 this.sessionIdCookieEnabled = sessionIdCookieEnabled; 081 } 082 083 public boolean isSessionIdUrlRewritingEnabled() { 084 return sessionIdUrlRewritingEnabled; 085 } 086 087 @SuppressWarnings({"UnusedDeclaration"}) 088 public void setSessionIdUrlRewritingEnabled(boolean sessionIdUrlRewritingEnabled) { 089 this.sessionIdUrlRewritingEnabled = sessionIdUrlRewritingEnabled; 090 } 091 092 private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) { 093 if (currentId == null) { 094 String msg = "sessionId cannot be null when persisting for subsequent requests."; 095 throw new IllegalArgumentException(msg); 096 } 097 Cookie template = getSessionIdCookie(); 098 Cookie cookie = new SimpleCookie(template); 099 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}