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     */
019    package org.apache.shiro.web.session.mgt;
020    
021    import org.apache.shiro.session.ExpiredSessionException;
022    import org.apache.shiro.session.InvalidSessionException;
023    import org.apache.shiro.session.Session;
024    import org.apache.shiro.session.mgt.DefaultSessionManager;
025    import org.apache.shiro.session.mgt.DelegatingSession;
026    import org.apache.shiro.session.mgt.SessionContext;
027    import org.apache.shiro.session.mgt.SessionKey;
028    import org.apache.shiro.web.servlet.Cookie;
029    import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
030    import org.apache.shiro.web.servlet.ShiroHttpSession;
031    import org.apache.shiro.web.servlet.SimpleCookie;
032    import org.apache.shiro.web.util.WebUtils;
033    import org.slf4j.Logger;
034    import org.slf4j.LoggerFactory;
035    
036    import javax.servlet.ServletRequest;
037    import javax.servlet.ServletResponse;
038    import javax.servlet.http.HttpServletRequest;
039    import javax.servlet.http.HttpServletResponse;
040    import 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     */
048    public 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    
055        public DefaultWebSessionManager() {
056            Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
057            cookie.setHttpOnly(true); //more secure, protects against XSS attacks
058            this.sessionIdCookie = cookie;
059            this.sessionIdCookieEnabled = true;
060        }
061    
062        public Cookie getSessionIdCookie() {
063            return sessionIdCookie;
064        }
065    
066        @SuppressWarnings({"UnusedDeclaration"})
067        public void setSessionIdCookie(Cookie sessionIdCookie) {
068            this.sessionIdCookie = sessionIdCookie;
069        }
070    
071        public boolean isSessionIdCookieEnabled() {
072            return sessionIdCookieEnabled;
073        }
074    
075        @SuppressWarnings({"UnusedDeclaration"})
076        public void setSessionIdCookieEnabled(boolean sessionIdCookieEnabled) {
077            this.sessionIdCookieEnabled = sessionIdCookieEnabled;
078        }
079    
080        private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
081            if (currentId == null) {
082                String msg = "sessionId cannot be null when persisting for subsequent requests.";
083                throw new IllegalArgumentException(msg);
084            }
085            Cookie template = getSessionIdCookie();
086            Cookie cookie = new SimpleCookie(template);
087            String idString = currentId.toString();
088            cookie.setValue(idString);
089            cookie.saveTo(request, response);
090            log.trace("Set session ID cookie for session with id {}", idString);
091        }
092    
093        private void removeSessionIdCookie(HttpServletRequest request, HttpServletResponse response) {
094            getSessionIdCookie().removeFrom(request, response);
095        }
096    
097        private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
098            if (!isSessionIdCookieEnabled()) {
099                log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
100                return null;
101            }
102            if (!(request instanceof HttpServletRequest)) {
103                log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie.  Returning null.");
104                return null;
105            }
106            HttpServletRequest httpRequest = (HttpServletRequest) request;
107            return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
108        }
109    
110        private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
111    
112            String id = getSessionIdCookieValue(request, response);
113            if (id != null) {
114                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
115                        ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
116            } else {
117                //not in a cookie, or cookie is disabled - try the request URI as a fallback (i.e. due to URL rewriting):
118    
119                //try the URI path segment parameters first:
120                id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
121    
122                if (id == null) {
123                    //not a URI path segment parameter, try the query parameters:
124                    String name = getSessionIdName();
125                    id = request.getParameter(name);
126                    if (id == null) {
127                        //try lowercase:
128                        id = request.getParameter(name.toLowerCase());
129                    }
130                }
131                if (id != null) {
132                    request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
133                            ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
134                }
135            }
136            if (id != null) {
137                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
138                //automatically mark it valid here.  If it is invalid, the
139                //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
140                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
141            }
142            return id;
143        }
144    
145        //SHIRO-351
146        //also see http://cdivilly.wordpress.com/2011/04/22/java-servlets-uri-parameters/
147        //since 1.2.2
148        private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) {
149    
150            if (!(servletRequest instanceof HttpServletRequest)) {
151                return null;
152            }
153            HttpServletRequest request = (HttpServletRequest)servletRequest;
154            String uri = request.getRequestURI();
155            if (uri == null) {
156                return null;
157            }
158    
159            int queryStartIndex = uri.indexOf('?');
160            if (queryStartIndex >= 0) { //get rid of the query string
161                uri = uri.substring(0, queryStartIndex);
162            }
163    
164            int index = uri.indexOf(';'); //now check for path segment parameters:
165            if (index < 0) {
166                //no path segment params - return:
167                return null;
168            }
169    
170            //there are path segment params, let's get the last one that may exist:
171    
172            final String TOKEN = paramName + "=";
173    
174            uri = uri.substring(index+1); //uri now contains only the path segment params
175    
176            //we only care about the last JSESSIONID param:
177            index = uri.lastIndexOf(TOKEN);
178            if (index < 0) {
179                //no segment param:
180                return null;
181            }
182    
183            uri = uri.substring(index + TOKEN.length());
184    
185            index = uri.indexOf(';'); //strip off any remaining segment params:
186            if(index >= 0) {
187                uri = uri.substring(0, index);
188            }
189    
190            return uri; //what remains is the value
191        }
192    
193        //since 1.2.1
194        private String getSessionIdName() {
195            String name = this.sessionIdCookie != null ? this.sessionIdCookie.getName() : null;
196            if (name == null) {
197                name = ShiroHttpSession.DEFAULT_SESSION_ID_NAME;
198            }
199            return name;
200        }
201    
202        protected Session createExposedSession(Session session, SessionContext context) {
203            if (!WebUtils.isWeb(context)) {
204                return super.createExposedSession(session, context);
205            }
206            ServletRequest request = WebUtils.getRequest(context);
207            ServletResponse response = WebUtils.getResponse(context);
208            SessionKey key = new WebSessionKey(session.getId(), request, response);
209            return new DelegatingSession(this, key);
210        }
211    
212        protected Session createExposedSession(Session session, SessionKey key) {
213            if (!WebUtils.isWeb(key)) {
214                return super.createExposedSession(session, key);
215            }
216    
217            ServletRequest request = WebUtils.getRequest(key);
218            ServletResponse response = WebUtils.getResponse(key);
219            SessionKey sessionKey = new WebSessionKey(session.getId(), request, response);
220            return new DelegatingSession(this, sessionKey);
221        }
222    
223        /**
224         * Stores the Session's ID, usually as a Cookie, to associate with future requests.
225         *
226         * @param session the session that was just {@link #createSession created}.
227         */
228        @Override
229        protected void onStart(Session session, SessionContext context) {
230            super.onStart(session, context);
231    
232            if (!WebUtils.isHttp(context)) {
233                log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " +
234                        "pair. No session ID cookie will be set.");
235                return;
236    
237            }
238            HttpServletRequest request = WebUtils.getHttpRequest(context);
239            HttpServletResponse response = WebUtils.getHttpResponse(context);
240    
241            if (isSessionIdCookieEnabled()) {
242                Serializable sessionId = session.getId();
243                storeSessionId(sessionId, request, response);
244            } else {
245                log.debug("Session ID cookie is disabled.  No cookie has been set for new session with id {}", session.getId());
246            }
247    
248            request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
249            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
250        }
251    
252        @Override
253        public Serializable getSessionId(SessionKey key) {
254            Serializable id = super.getSessionId(key);
255            if (id == null && WebUtils.isWeb(key)) {
256                ServletRequest request = WebUtils.getRequest(key);
257                ServletResponse response = WebUtils.getResponse(key);
258                id = getSessionId(request, response);
259            }
260            return id;
261        }
262    
263        protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
264            return getReferencedSessionId(request, response);
265        }
266    
267        @Override
268        protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
269            super.onExpiration(s, ese, key);
270            onInvalidation(key);
271        }
272    
273        @Override
274        protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) {
275            super.onInvalidation(session, ise, key);
276            onInvalidation(key);
277        }
278    
279        private void onInvalidation(SessionKey key) {
280            ServletRequest request = WebUtils.getRequest(key);
281            if (request != null) {
282                request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID);
283            }
284            if (WebUtils.isHttp(key)) {
285                log.debug("Referenced session was invalid.  Removing session ID cookie.");
286                removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key));
287            } else {
288                log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
289                        "pair. Session ID cookie will not be removed due to invalidated session.");
290            }
291        }
292    
293        @Override
294        protected void onStop(Session session, SessionKey key) {
295            super.onStop(session, key);
296            if (WebUtils.isHttp(key)) {
297                HttpServletRequest request = WebUtils.getHttpRequest(key);
298                HttpServletResponse response = WebUtils.getHttpResponse(key);
299                log.debug("Session has been stopped (subject logout or explicit stop).  Removing session ID cookie.");
300                removeSessionIdCookie(request, response);
301            } else {
302                log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
303                        "pair. Session ID cookie will not be removed due to stopped session.");
304            }
305        }
306    
307        /**
308         * This is a native session manager implementation, so this method returns {@code false} always.
309         *
310         * @return {@code false} always
311         * @since 1.2
312         */
313        public boolean isServletContainerSessions() {
314            return false;
315        }
316    }