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 {
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 params as a fallback (i.e. URL rewriting):
118                id = request.getParameter(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
119                if (id == null) {
120                    //try lowercase:
121                    id = request.getParameter(ShiroHttpSession.DEFAULT_SESSION_ID_NAME.toLowerCase());
122                }
123                if (id != null) {
124                    request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
125                            ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
126                }
127            }
128            if (id != null) {
129                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
130                //automatically mark it valid here.  If it is invalid, the
131                //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
132                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
133            }
134            return id;
135        }
136    
137        protected Session createExposedSession(Session session, SessionContext context) {
138            if (!WebUtils.isWeb(context)) {
139                return super.createExposedSession(session, context);
140            }
141            ServletRequest request = WebUtils.getRequest(context);
142            ServletResponse response = WebUtils.getResponse(context);
143            SessionKey key = new WebSessionKey(session.getId(), request, response);
144            return new DelegatingSession(this, key);
145        }
146    
147        protected Session createExposedSession(Session session, SessionKey key) {
148            if (!WebUtils.isWeb(key)) {
149                return super.createExposedSession(session, key);
150            }
151    
152            ServletRequest request = WebUtils.getRequest(key);
153            ServletResponse response = WebUtils.getResponse(key);
154            SessionKey sessionKey = new WebSessionKey(session.getId(), request, response);
155            return new DelegatingSession(this, sessionKey);
156        }
157    
158        /**
159         * Stores the Session's ID, usually as a Cookie, to associate with future requests.
160         *
161         * @param session the session that was just {@link #createSession created}.
162         */
163        @Override
164        protected void onStart(Session session, SessionContext context) {
165            super.onStart(session, context);
166    
167            if (!WebUtils.isHttp(context)) {
168                log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " +
169                        "pair. No session ID cookie will be set.");
170                return;
171    
172            }
173            HttpServletRequest request = WebUtils.getHttpRequest(context);
174            HttpServletResponse response = WebUtils.getHttpResponse(context);
175    
176            if (isSessionIdCookieEnabled()) {
177                Serializable sessionId = session.getId();
178                storeSessionId(sessionId, request, response);
179            } else {
180                log.debug("Session ID cookie is disabled.  No cookie has been set for new session with id {}", session.getId());
181            }
182    
183            request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
184            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
185        }
186    
187        @Override
188        public Serializable getSessionId(SessionKey key) {
189            Serializable id = super.getSessionId(key);
190            if (id == null && WebUtils.isWeb(key)) {
191                ServletRequest request = WebUtils.getRequest(key);
192                ServletResponse response = WebUtils.getResponse(key);
193                id = getSessionId(request, response);
194            }
195            return id;
196        }
197    
198        protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
199            return getReferencedSessionId(request, response);
200        }
201    
202        @Override
203        protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
204            super.onExpiration(s, ese, key);
205            onInvalidation(key);
206        }
207    
208        @Override
209        protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) {
210            super.onInvalidation(session, ise, key);
211            onInvalidation(key);
212        }
213    
214        private void onInvalidation(SessionKey key) {
215            ServletRequest request = WebUtils.getRequest(key);
216            if (request != null) {
217                request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID);
218            }
219            if (WebUtils.isHttp(key)) {
220                log.debug("Referenced session was invalid.  Removing session ID cookie.");
221                removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key));
222            } else {
223                log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
224                        "pair. Session ID cookie will not be removed due to invalidated session.");
225            }
226        }
227    
228        @Override
229        protected void onStop(Session session, SessionKey key) {
230            super.onStop(session, key);
231            if (WebUtils.isHttp(key)) {
232                HttpServletRequest request = WebUtils.getHttpRequest(key);
233                HttpServletResponse response = WebUtils.getHttpResponse(key);
234                log.debug("Session has been stopped (subject logout or explicit stop).  Removing session ID cookie.");
235                removeSessionIdCookie(request, response);
236            } else {
237                log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
238                        "pair. Session ID cookie will not be removed due to stopped session.");
239            }
240        }
241    }