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}