/**
+ * The request attribute key for the session manager.
+ * This one is a Tomcat extension to the Servlet spec.
+ */
+ public static final String SSL_SESSION_MGR_ATTR =
+ "javax.servlet.request.ssl_session_mgr";
+
+
+ /**
* The servlet context attribute under which the managed bean Registry
* will be stored for privileged contexts (if enabled).
*/
package org.apache.catalina.connector;
import java.io.IOException;
+import java.util.EnumSet;
+
+import javax.servlet.SessionTrackingMode;
import org.apache.catalina.CometEvent;
import org.apache.catalina.Context;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.Cookies;
import org.apache.tomcat.util.http.ServerCookie;
+import org.apache.tomcat.util.net.SSLSupport;
import org.apache.tomcat.util.net.SocketStatus;
// -------------------------------------------------------------- Constants
+ private static final EnumSet<SessionTrackingMode> SSL_ONLY =
+ EnumSet.of(SessionTrackingMode.SSL);
public static final int ADAPTER_NOTES = 1;
// Parse session Id
parseSessionCookiesId(req, request);
-
+ parseSessionSslId(request);
return true;
}
/**
+ * Look for SSL sesison ID if required. Only look for SSL Session ID if it
+ * is the only tracking method enabled.
+ */
+ protected void parseSessionSslId(Request request) {
+ if (request.getRequestedSessionId() == null &&
+ SSL_ONLY.equals(request.getServletContext()
+ .getEffectiveSessionTrackingModes()) &&
+ Boolean.TRUE.equals(
+ request.getConnector().getAttribute("SSLEnabled"))) {
+ // TODO Is there a better way to map SSL sessions to our sesison ID?
+ // TODO The request.getAttribute() will cause a number of other SSL
+ // attribute to be populated. Is this a performance concern?
+ request.setRequestedSessionId(
+ request.getAttribute(SSLSupport.SESSION_ID_KEY).toString());
+ request.setRequestedSessionSSL(true);
+ }
+ }
+
+
+ /**
* Parse session id in URL.
*/
protected void parseSessionId(org.apache.coyote.Request req, Request request) {
ByteChunk uriBC = req.requestURI().getByteChunk();
int semicolon = uriBC.indexOf(match, 0, match.length(), 0);
- if (semicolon > 0) {
+ if (semicolon > 0 &&
+ request.getServletContext().getEffectiveSessionTrackingModes()
+ .contains(SessionTrackingMode.URL)) {
// Parse session ID, and extract it from the decoded request URI
int start = uriBC.getStart();
// from a parent context with a session ID may be present which would
// overwrite the valid session ID encoded in the URL
Context context = (Context) request.getMappingData().context;
- if (context != null && !context.getCookies())
+ if (context != null && !context.getServletContext()
+ .getEffectiveSessionTrackingModes().contains(
+ SessionTrackingMode.COOKIE))
return;
// Parse session id from cookies
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletResponse;
import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
+ * Was the requested session ID obtained from the SSL session?
+ */
+ protected boolean requestedSessionSSL = false;
+
+
+ /**
* Parse locales.
*/
protected boolean localesParsed = false;
}
public ServletContext getServletContext() {
- // TODO SERVLET3
- return null;
+ return context.getServletContext();
}
public boolean isAsyncStarted() {
/**
+ * Set a flag indicating whether or not the requested session ID for this
+ * request came in through SSL. This is normally called by the
+ * HTTP Connector, when it parses the request headers.
+ *
+ * @param flag The new flag
+ */
+ public void setRequestedSessionSSL(boolean flag) {
+
+ this.requestedSessionSSL = flag;
+
+ }
+
+
+ /**
* Set the unparsed request URI for this Request. This will normally be
* called by the HTTP Connector, when it parses the request headers.
*
coyoteRequest.action(ActionCode.ACTION_COMET_SETTIMEOUT,new Long(timeout));
}
+ /**
+ * Not part of Servlet 3 spec but probably should be.
+ * @return
+ */
+ public boolean isRequestedSessionIdFromSSL() {
+ return requestedSessionSSL;
+ }
+
+
// ------------------------------------------------------ Protected Methods
if (!create)
return (null);
if ((context != null) && (response != null) &&
- context.getCookies() &&
+ context.getServletContext().getEffectiveSessionTrackingModes().
+ contains(SessionTrackingMode.COOKIE) &&
response.getResponse().isCommitted()) {
throw new IllegalStateException
(sm.getString("coyoteRequest.sessionCreateCommitted"));
// Attempt to reuse session id if one was submitted in a cookie
// Do not reuse the session id if it is from a URL, to prevent possible
// phishing attacks
- if (connector.getEmptySessionPath()
- && isRequestedSessionIdFromCookie()) {
+ // Use the SSL session ID if one is present.
+ if ((connector.getEmptySessionPath()
+ && isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {
session = manager.createSession(getRequestedSessionId());
+ if (requestedSessionSSL) {
+ coyoteRequest.action(ActionCode.ACTION_REQ_SSL_SESSION_MGR,
+ null);
+ session.setNote(
+ org.apache.catalina.session.Constants.SESS_SSL_MGMT,
+ getAttribute(Globals.SSL_SESSION_MGR_ATTR));
+ }
} else {
session = manager.createSession(null);
}
// Creating a new session cookie based on that session
if ((session != null) && (getContext() != null)
- && getContext().getCookies()) {
+ && getContext().getServletContext().
+ getEffectiveSessionTrackingModes().contains(
+ SessionTrackingMode.COOKIE)) {
Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,
session.getIdInternal());
configureSessionCookie(cookie);
import java.util.Vector;
import javax.servlet.ServletOutputStream;
+import javax.servlet.SessionTrackingMode;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
if (hreq.isRequestedSessionIdFromCookie())
return (false);
+ // Is URL encoding permitted
+ if (!hreq.getServletContext().getEffectiveSessionTrackingModes().
+ contains(SessionTrackingMode.URL))
+ return false;
+
if (SecurityUtil.isPackageProtectionEnabled()) {
return (
AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
import javax.servlet.SessionTrackingMode;
import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Wrapper;
+import org.apache.catalina.connector.Connector;
import org.apache.catalina.deploy.ApplicationParameter;
import org.apache.catalina.util.Enumerator;
import org.apache.catalina.util.ResourceSet;
super();
this.context = context;
this.basePath = basePath;
+
+ // Populate default session tracking modes
+ populateDefaultSessionTrackingModes();
}
* Session Cookie config
*/
private SessionCookieConfig sessionCookieConfig;
+
+ /**
+ * Session tracking modes
+ */
+ private EnumSet<SessionTrackingMode> sessionTrackingModes = null;
+ private EnumSet<SessionTrackingMode> defaultSessionTrackingModes = null;
// --------------------------------------------------------- Public Methods
}
+ /**
+ * By default {@link SessionTrackingMode#URL} is always supported, {@link
+ * SessionTrackingMode#COOKIE} is supported unless the <code>cookies</code>
+ * attribute has been set to <code>false</code> for the context and {@link
+ * SessionTrackingMode#SSL} is supported if at least one of the connectors
+ * used by this context has the attribute <code>SSLEnabled</code> set to
+ * <code>true</code>.
+ */
public EnumSet<SessionTrackingMode> getDefaultSessionTrackingModes() {
- // TODO SERVLET3
- return null;
+ return defaultSessionTrackingModes;
}
+ private void populateDefaultSessionTrackingModes() {
+ // URL re-writing is always enabled by default
+ defaultSessionTrackingModes = EnumSet.of(SessionTrackingMode.URL);
+
+ if (context.getCookies()) {
+ defaultSessionTrackingModes.add(SessionTrackingMode.COOKIE);
+ }
+
+ // Context > Host > Engine > Service
+ Connector[] connectors = ((Engine) context.getParent().getParent())
+ .getService().findConnectors();
+ // Need at least one SSL enabled connector to use the SSL session ID.
+ // has to be SSL enabled so we can close the SSL session.
+ // TODO extend this for SSL sessions managed by accelerators, web
+ // servers etc
+ for (Connector connector : connectors) {
+ if (Boolean.TRUE.equals(connector.getAttribute("SSLEnabled"))) {
+ defaultSessionTrackingModes.add(SessionTrackingMode.SSL);
+ break;
+ }
+ }
+ }
+ /**
+ * Return the supplied value if one was previously set, else return the
+ * defaults.
+ */
public EnumSet<SessionTrackingMode> getEffectiveSessionTrackingModes() {
- // TODO SERVLET3
- return null;
+ if (sessionTrackingModes != null) {
+ return sessionTrackingModes;
+ }
+ return defaultSessionTrackingModes;
}
}
+ /**
+ * @throws IllegalStateException if the context has already been initialised
+ * @throws IllegalArgumentException TODO SERVLET3 Something to do with SSL
+ * but the spec language is not clear
+ * If an unsupported tracking mode is
+ * requested
+ */
public void setSessionTrackingModes(
EnumSet<SessionTrackingMode> sessionTrackingModes) {
- // TODO SERVLET3
+
+ if (context.getAvailable()) {
+ throw new IllegalStateException(
+ sm.getString("applicationContext.setSessionTracking.ise",
+ getContextPath()));
+ }
+
+ // Check that only supported tracking modes have been requested
+ for (SessionTrackingMode sessionTrackingMode : sessionTrackingModes) {
+ if (!defaultSessionTrackingModes.contains(sessionTrackingMode)) {
+ throw new IllegalArgumentException(sm.getString(
+ "applicationContext.setSessionTracking.iae",
+ sessionTrackingMode.toString(), getContextPath()));
+ }
+ }
+ // TODO SERVLET3 - The SSL test
+
+ this.sessionTrackingModes = sessionTrackingModes;
}
classCache.put("getRealPath", clazz);
classCache.put("getAttribute", clazz);
classCache.put("log", clazz);
+ classCache.put("getDefaultSessionTrackingModes", clazz);
+ classCache.put("getEffectiveSessionTrackingModes", clazz);
+ classCache.put("setSessionTrackingModes", clazz);
}
public EnumSet<SessionTrackingMode> getDefaultSessionTrackingModes() {
- // TODO SERVLET3
- return null;
+ if (SecurityUtil.isPackageProtectionEnabled()) {
+ return (EnumSet<SessionTrackingMode>)
+ doPrivileged("getDefaultSessionTrackingModes", null);
+ } else {
+ return context.getDefaultSessionTrackingModes();
+ }
}
public EnumSet<SessionTrackingMode> getEffectiveSessionTrackingModes() {
- // TODO SERVLET3
- return null;
+ if (SecurityUtil.isPackageProtectionEnabled()) {
+ return (EnumSet<SessionTrackingMode>)
+ doPrivileged("getEffectiveSessionTrackingModes", null);
+ } else {
+ return context.getEffectiveSessionTrackingModes();
+ }
}
public void setSessionTrackingModes(
EnumSet<SessionTrackingMode> sessionTrackingModes) {
- // TODO SERVLET3
+ if (SecurityUtil.isPackageProtectionEnabled()) {
+ doPrivileged("setSessionTrackingModes",
+ new Object[]{sessionTrackingModes});
+ } else {
+ context.setSessionTrackingModes(sessionTrackingModes);
+ }
}
applicationContext.requestDispatcher.iae=Path {0} does not start with a "/" character
applicationContext.resourcePaths.iae=Path {0} does not start with a "/" character
applicationContext.setAttribute.namenull=Name cannot be null
+applicationContext.setSessionTracking.ise=The session tracking modes for context {0} cannot be set whilst the context is running
+applicationContext.setSessionTracking.iae=The session tracking mode {0} requested for context {1} is not supported by that context
applicationDispatcher.allocateException=Allocate exception for servlet {0}
applicationDispatcher.deallocateException=Deallocate exception for servlet {0}
applicationDispatcher.forward.ise=Cannot forward after response has been committed
import java.io.IOException;
import javax.servlet.ServletException;
+import javax.servlet.SessionTrackingMode;
import javax.servlet.http.Cookie;
import org.apache.catalina.Container;
Response response, String sessionId) {
if (response != null) {
Context context = request.getContext();
- if (context.getCookies()) {
+ if (context.getServletContext().getEffectiveSessionTrackingModes()
+ .contains(SessionTrackingMode.COOKIE)) {
// set a new session cookie
Cookie newCookie = new Cookie(Globals.SESSION_COOKIE_NAME,
sessionId);
public static final String Package = "org.apache.catalina.session";
+ /**
+ * Name of note containing SSL session manager
+ */
+ public static final String SESS_SSL_MGMT =
+ "org.apache.catalina.session.SSL_MGMT";
+
}
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.net.SSLSessionManager;
/**
public void remove(Session session) {
sessions.remove(session.getIdInternal());
+ // Close the underlying SSL session
+ SSLSessionManager mgr =
+ (SSLSessionManager) session.getNote(Constants.SESS_SSL_MGMT);
+ if (mgr != null) {
+ mgr.invalidateSession();
+ }
}
package org.apache.coyote;
+import org.apache.tomcat.util.net.SSLSessionManager;
+
/**
* Enumerated class containing the adapter event codes.
*/
public static final ActionCode ACTION_COMET_SETTIMEOUT = new ActionCode(25);
+ /**
+ * Callback for lazy evaluation - obtain the SSL Session Manager
+ */
+ public static final ActionCode ACTION_REQ_SSL_SESSION_MGR =
+ new ActionCode(26);
+
// ----------------------------------------------------------- Constructors
int code;
//no op
} else if (actionCode == ActionCode.ACTION_COMET_SETTIMEOUT) {
//no op
+ } else if (actionCode == ActionCode.ACTION_REQ_SSL_SESSION_MGR) {
+ //TODO SERVLET3 provide a hook to enable the SSL session to be
+ // invalidated
}
}
RequestInfo rp = request.getRequestProcessor();
if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) //async handling
attach.setTimeout(timeout);
+ } else if (actionCode == ActionCode.ACTION_REQ_SSL_SESSION_MGR) {
+ if( sslSupport != null) {
+ request.setAttribute(SSLSupport.SESSION_MGR, sslSupport);
+ }
}
-
}
InternalInputBuffer internalBuffer = (InternalInputBuffer)
request.getInputBuffer();
internalBuffer.addActiveFilter(savedBody);
+ } else if (actionCode == ActionCode.ACTION_REQ_SSL_SESSION_MGR) {
+ if( sslSupport != null) {
+ request.setAttribute(SSLSupport.SESSION_MGR, sslSupport);
+ }
}
}
*/
public static final String SESSION_ID_KEY = "javax.servlet.request.ssl_session";
+ /**
+ * The request attribute key for the session manager.
+ * This one is a Tomcat extension to the Servlet spec.
+ */
+ public static final String SESSION_MGR =
+ "javax.servlet.request.ssl_session_mgr";
+
// ----------------------------------------------------------------- Fields
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+/**
+ * Defines an interface used to manage SSL sessions. The manager operates on a
+ * single session.
+ *
+ * $Id$
+ */
+public interface SSLSessionManager {
+ /**
+ * Invalidate the specified SSL session
+ * @param sessionId The ID of the session to invalidate.
+ */
+ public void invalidateSession();
+}
public static final String SESSION_ID_KEY = "javax.servlet.request.ssl_session";
/**
+ * The request attribute key for the session manager.
+ * This one is a Tomcat extension to the Servlet spec.
+ */
+ public static final String SESSION_MGR =
+ "javax.servlet.request.ssl_session_mgr";
+
+
+ /**
* A mapping table to determine the number of effective bits in the key
* when using a cipher suite containing the specified cipher name. The
* underlying data came from the TLS Specification (RFC 2246), Appendix C.
import javax.net.ssl.SSLSocket;
import javax.security.cert.X509Certificate;
+import org.apache.tomcat.util.net.SSLSessionManager;
import org.apache.tomcat.util.net.SSLSupport;
/** JSSESupport
Parts cribbed from CertificatesValve
*/
-class JSSESupport implements SSLSupport {
+class JSSESupport implements SSLSupport, SSLSessionManager {
private static org.apache.juli.logging.Log log =
org.apache.juli.logging.LogFactory.getLog(JSSESupport.class);
}
}
+ /**
+ * Invalidate the session this support object is associated with.
+ */
+ public void invalidateSession() {
+ session.invalidate();
+ }
}
<subsection name="Edit the Tomcat Configuration File">
<p>If you are using APR, you have the option of configuring an alternative engine to OpenSSL.
<source>
-<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="someengine" SSLRandomSeed="somedevice" />
+<Listener className="org.apache.catalina.core.AprLifecycleListener"
+ SSLEngine="someengine" SSLRandomSeed="somedevice" />
</source>
The default value is
<source>
-<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" SSLRandomSeed="builtin" />
+<Listener className="org.apache.catalina.core.AprLifecycleListener"
+ SSLEngine="on" SSLRandomSeed="builtin" />
</source>
So to use SSL under APR, make sure the SSLEngine attribute is set to something other than <code>off</code>.
The default value is <code>on</code> and if you specify another value, it has to be a valid engine name.
<br/>
If you haven't compiled in SSL support into your Tomcat Native library, then you can turn this initialization off
<source>
-<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="off" />
+<Listener className="org.apache.catalina.core.AprLifecycleListener"
+ SSLEngine="off" />
</source>
SSLRandomSeed allows to specify a source of entropy. Productive system needs a reliable source of entropy
but entropy may need a lot of time to be collected therefore test systems could use no blocking entropy
</section>
+<section name="Using the SSL for session tracking in your application">
+ <p>This is a new feature in the Servlet 3.0 specification. Because is uses the
+ SSL session ID associated with the physical client server connection there
+ are a number of limitations. They are:
+ <ul>
+ <li>The SSL connection must be managed by Tomcat, i.e. Tomcat must have a
+ connector with the attribute <strong>SSLEnabled</strong> set to
+ <code>true</code>. This is to enable Tomcat to invalidate the SSL
+ session if the HTTP session is invalidated. If SSL conections are
+ managed by a proxy or a hardware accelerator this is not possibe.</li>
+ <li>It cannot be used in conjunction with session replication as the SSL
+ session IDs will be different on each node.</li>
+ <li>When <code>session.invalidate()</code> is called within the application
+ <code>response.setHeader("Connection", "close")</code> must also be
+ called as invalidating the session does not affect any current
+ connections.</li>
+ <li>HTTP session timeouts, keep-alive timeouts and SSL session timeouts
+ should be consistent. Note that the default JSSE SSL session timeout
+ (24 hours) is significantly longer than the default Tomcat HTTP Sesson
+ timeout (30 minutes).</li>
+ </ul>
+ </p>
+
+ <p>
+ To enable SSL session tracking you need to use a context listener to set the
+ tracking mode for the context to be just SSL (if any other tracking mode is
+ enabled, it will be used in preference). It might look something like:
+ <source>
+package org.apache.tomcat.example;
+
+import java.util.EnumSet;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.SessionTrackingMode;
+
+public class SessionTrackingModeListener implements ServletContextListener {
+
+ @Override
+ public void contextDestroyed(ServletContextEvent event) {
+ // Do nothing
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent event) {
+ ServletContext context = event.getServletContext();
+ EnumSet<SessionTrackingMode> modes =
+ EnumSet.of(SessionTrackingMode.SSL);
+
+ context.setSessionTrackingModes(modes);
+ }
+
+}
+ </source>
+ </p>
+ <p>Note: SSL session tracking is implemented for the BIO and NIO connetcors.
+ It is not yet implemented for the APR connector.</p>
+
+</section>
+
<section name="Miscellaneous Tips and Bits">
<p>To access the SSL session ID from the request, use:<br />