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 log = 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 cookie.setHttpOnly(true); //more secure, protects against XSS attacks 059 this.sessionIdCookie = cookie; 060 this.sessionIdCookieEnabled = true; 061 this.sessionIdUrlRewritingEnabled = false; 062 } 063 064 public Cookie getSessionIdCookie() { 065 return sessionIdCookie; 066 } 067 068 @SuppressWarnings({"UnusedDeclaration"}) 069 public void setSessionIdCookie(Cookie sessionIdCookie) { 070 this.sessionIdCookie = sessionIdCookie; 071 } 072 073 public boolean isSessionIdCookieEnabled() { 074 return sessionIdCookieEnabled; 075 } 076 077 @SuppressWarnings({"UnusedDeclaration"}) 078 public void setSessionIdCookieEnabled(boolean sessionIdCookieEnabled) { 079 this.sessionIdCookieEnabled = sessionIdCookieEnabled; 080 } 081 082 public boolean isSessionIdUrlRewritingEnabled() { 083 return sessionIdUrlRewritingEnabled; 084 } 085 086 @SuppressWarnings({"UnusedDeclaration"}) 087 public void setSessionIdUrlRewritingEnabled(boolean sessionIdUrlRewritingEnabled) { 088 this.sessionIdUrlRewritingEnabled = sessionIdUrlRewritingEnabled; 089 } 090 091 private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) { 092 if (currentId == null) { 093 String msg = "sessionId cannot be null when persisting for subsequent requests."; 094 throw new IllegalArgumentException(msg); 095 } 096 Cookie template = getSessionIdCookie(); 097 Cookie cookie = new SimpleCookie(template); 098 String idString = currentId.toString(); 099 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 && request instanceof HttpServletRequest) { 134 //not a URI path segment parameter, try the query parameters: 135 String name = getSessionIdName(); 136 HttpServletRequest httpServletRequest = WebUtils.toHttp(request); 137 String queryString = httpServletRequest.getQueryString(); 138 if (queryString != null && queryString.contains(name)) { 139 id = request.getParameter(name); 140 } 141 if (id == null && queryString != null && queryString.contains(name.toLowerCase())) { 142 //try lowercase: 143 id = request.getParameter(name.toLowerCase()); 144 } 145 } 146 if (id != null) { 147 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, 148 ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); 149 } 150 } 151 if (id != null) { 152 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); 153 //automatically mark it valid here. If it is invalid, the 154 //onUnknownSession method below will be invoked and we'll remove the attribute at that time. 155 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); 156 } 157 158 // always set rewrite flag - SHIRO-361 159 request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled()); 160 161 return id; 162 } 163 164 //SHIRO-351 165 //also see http://cdivilly.wordpress.com/2011/04/22/java-servlets-uri-parameters/ 166 //since 1.2.2 167 private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) { 168 169 if (!(servletRequest instanceof HttpServletRequest)) { 170 return null; 171 } 172 HttpServletRequest request = (HttpServletRequest)servletRequest; 173 String uri = request.getRequestURI(); 174 if (uri == null) { 175 return null; 176 } 177 178 int queryStartIndex = uri.indexOf('?'); 179 if (queryStartIndex >= 0) { //get rid of the query string 180 uri = uri.substring(0, queryStartIndex); 181 } 182 183 int index = uri.indexOf(';'); //now check for path segment parameters: 184 if (index < 0) { 185 //no path segment params - return: 186 return null; 187 } 188 189 //there are path segment params, let's get the last one that may exist: 190 191 final String TOKEN = paramName + "="; 192 193 uri = uri.substring(index+1); //uri now contains only the path segment params 194 195 //we only care about the last JSESSIONID param: 196 index = uri.lastIndexOf(TOKEN); 197 if (index < 0) { 198 //no segment param: 199 return null; 200 } 201 202 uri = uri.substring(index + TOKEN.length()); 203 204 index = uri.indexOf(';'); //strip off any remaining segment params: 205 if(index >= 0) { 206 uri = uri.substring(0, index); 207 } 208 209 return uri; //what remains is the value 210 } 211 212 //since 1.2.1 213 private String getSessionIdName() { 214 String name = this.sessionIdCookie != null ? this.sessionIdCookie.getName() : null; 215 if (name == null) { 216 name = ShiroHttpSession.DEFAULT_SESSION_ID_NAME; 217 } 218 return name; 219 } 220 221 protected Session createExposedSession(Session session, SessionContext context) { 222 if (!WebUtils.isWeb(context)) { 223 return super.createExposedSession(session, context); 224 } 225 ServletRequest request = WebUtils.getRequest(context); 226 ServletResponse response = WebUtils.getResponse(context); 227 SessionKey key = new WebSessionKey(session.getId(), request, response); 228 return new DelegatingSession(this, key); 229 } 230 231 protected Session createExposedSession(Session session, SessionKey key) { 232 if (!WebUtils.isWeb(key)) { 233 return super.createExposedSession(session, key); 234 } 235 236 ServletRequest request = WebUtils.getRequest(key); 237 ServletResponse response = WebUtils.getResponse(key); 238 SessionKey sessionKey = new WebSessionKey(session.getId(), request, response); 239 return new DelegatingSession(this, sessionKey); 240 } 241 242 /** 243 * Stores the Session's ID, usually as a Cookie, to associate with future requests. 244 * 245 * @param session the session that was just {@link #createSession created}. 246 */ 247 @Override 248 protected void onStart(Session session, SessionContext context) { 249 super.onStart(session, context); 250 251 if (!WebUtils.isHttp(context)) { 252 log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " + 253 "pair. No session ID cookie will be set."); 254 return; 255 256 } 257 HttpServletRequest request = WebUtils.getHttpRequest(context); 258 HttpServletResponse response = WebUtils.getHttpResponse(context); 259 260 if (isSessionIdCookieEnabled()) { 261 Serializable sessionId = session.getId(); 262 storeSessionId(sessionId, request, response); 263 } else { 264 log.debug("Session ID cookie is disabled. No cookie has been set for new session with id {}", session.getId()); 265 } 266 267 request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE); 268 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE); 269 } 270 271 @Override 272 public Serializable getSessionId(SessionKey key) { 273 Serializable id = super.getSessionId(key); 274 if (id == null && WebUtils.isWeb(key)) { 275 ServletRequest request = WebUtils.getRequest(key); 276 ServletResponse response = WebUtils.getResponse(key); 277 id = getSessionId(request, response); 278 } 279 return id; 280 } 281 282 protected Serializable getSessionId(ServletRequest request, ServletResponse response) { 283 return getReferencedSessionId(request, response); 284 } 285 286 @Override 287 protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) { 288 super.onExpiration(s, ese, key); 289 onInvalidation(key); 290 } 291 292 @Override 293 protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) { 294 super.onInvalidation(session, ise, key); 295 onInvalidation(key); 296 } 297 298 private void onInvalidation(SessionKey key) { 299 ServletRequest request = WebUtils.getRequest(key); 300 if (request != null) { 301 request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID); 302 } 303 if (WebUtils.isHttp(key)) { 304 log.debug("Referenced session was invalid. Removing session ID cookie."); 305 removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key)); 306 } else { 307 log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " + 308 "pair. Session ID cookie will not be removed due to invalidated session."); 309 } 310 } 311 312 @Override 313 protected void onStop(Session session, SessionKey key) { 314 super.onStop(session, key); 315 if (WebUtils.isHttp(key)) { 316 HttpServletRequest request = WebUtils.getHttpRequest(key); 317 HttpServletResponse response = WebUtils.getHttpResponse(key); 318 log.debug("Session has been stopped (subject logout or explicit stop). Removing session ID cookie."); 319 removeSessionIdCookie(request, response); 320 } else { 321 log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " + 322 "pair. Session ID cookie will not be removed due to stopped session."); 323 } 324 } 325 326 /** 327 * This is a native session manager implementation, so this method returns {@code false} always. 328 * 329 * @return {@code false} always 330 * @since 1.2 331 */ 332 public boolean isServletContainerSessions() { 333 return false; 334 } 335}