blob: d8cca44a9e35b4ecf6329d98395e55ecc2ccaa36 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.session;
import java.security.SecureRandom;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.SessionIdManager;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* AbstractSessionIdManager
*
* Manages session ids to ensure each session id within a context is unique, and that
* session ids can be shared across contexts (but not session contents).
*
* There is only 1 session id manager per Server instance.
*/
public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager
{
private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
protected Random _random;
protected boolean _weakRandom;
protected String _workerName;
protected String _workerAttr;
protected long _reseed=100000L;
protected Server _server;
protected SessionScavenger _scavenger;
/* ------------------------------------------------------------ */
public AbstractSessionIdManager(Server server)
{
_server = server;
}
/* ------------------------------------------------------------ */
public AbstractSessionIdManager(Server server, Random random)
{
this(server);
_random=random;
}
/* ------------------------------------------------------------ */
public void setServer (Server server)
{
_server = server;
}
/* ------------------------------------------------------------ */
public Server getServer ()
{
return _server;
}
/* ------------------------------------------------------------ */
/**
* @param scavenger
*/
public void setSessionScavenger (SessionScavenger scavenger)
{
_scavenger = scavenger;
_scavenger.setSessionIdManager(this);
}
/* ------------------------------------------------------------ */
/**
* Get the workname. If set, the workername is dot appended to the session
* ID and can be used to assist session affinity in a load balancer.
*
* @return String or null
*/
@Override
public String getWorkerName()
{
return _workerName;
}
/* ------------------------------------------------------------ */
/**
* Set the workername. If set, the workername is dot appended to the session
* ID and can be used to assist session affinity in a load balancer.
* A worker name starting with $ is used as a request attribute name to
* lookup the worker name that can be dynamically set by a request
* Customizer.
*
* @param workerName the name of the worker
*/
public void setWorkerName(String workerName)
{
if (isRunning())
throw new IllegalStateException(getState());
if (workerName.contains("."))
throw new IllegalArgumentException("Name cannot contain '.'");
_workerName=workerName;
}
/* ------------------------------------------------------------ */
public Random getRandom()
{
return _random;
}
/* ------------------------------------------------------------ */
public synchronized void setRandom(Random random)
{
_random=random;
_weakRandom=false;
}
/* ------------------------------------------------------------ */
/**
* @return the reseed probability
*/
public long getReseed()
{
return _reseed;
}
/* ------------------------------------------------------------ */
/** Set the reseed probability.
* @param reseed If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded.
*/
public void setReseed(long reseed)
{
_reseed = reseed;
}
/* ------------------------------------------------------------ */
/**
* Create a new session id if necessary.
*
* @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
*/
@Override
public String newSessionId(HttpServletRequest request, long created)
{
synchronized (this)
{
if (request==null)
return newSessionId(created);
// A requested session ID can only be used if it is in use already.
String requested_id=request.getRequestedSessionId();
if (requested_id!=null)
{
String cluster_id=getId(requested_id);
if (isIdInUse(cluster_id))
return cluster_id;
}
// Else reuse any new session ID already defined for this request.
String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
if (new_id!=null&&isIdInUse(new_id))
return new_id;
// pick a new unique ID!
String id = newSessionId(request.hashCode());
request.setAttribute(__NEW_SESSION_ID,id);
return id;
}
}
/* ------------------------------------------------------------ */
public String newSessionId(long seedTerm)
{
// pick a new unique ID!
String id=null;
while (id==null||id.length()==0||isIdInUse(id))
{
long r0=_weakRandom
?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
:_random.nextLong();
if (r0<0)
r0=-r0;
// random chance to reseed
if (_reseed>0 && (r0%_reseed)== 1L)
{
if (LOG.isDebugEnabled())
LOG.debug("Reseeding {}",this);
if (_random instanceof SecureRandom)
{
SecureRandom secure = (SecureRandom)_random;
secure.setSeed(secure.generateSeed(8));
}
else
{
_random.setSeed(_random.nextLong()^System.currentTimeMillis()^seedTerm^Runtime.getRuntime().freeMemory());
}
}
long r1=_weakRandom
?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
:_random.nextLong();
if (r1<0)
r1=-r1;
id=Long.toString(r0,36)+Long.toString(r1,36);
//add in the id of the node to ensure unique id across cluster
//NOTE this is different to the node suffix which denotes which node the request was received on
if (_workerName!=null)
id=_workerName + id;
}
return id;
}
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
{
if (_server == null)
throw new IllegalStateException("No Server for SessionIdManager");
initRandom();
_workerAttr=(_workerName!=null && _workerName.startsWith("$"))?_workerName.substring(1):null;
if (_scavenger == null)
{
LOG.warn("No SessionScavenger set, using defaults");
_scavenger = new SessionScavenger();
_scavenger.setSessionIdManager(this);
}
_scavenger.start();
}
/* ------------------------------------------------------------ */
@Override
protected void doStop() throws Exception
{
_scavenger.stop();
}
/* ------------------------------------------------------------ */
/**
* Set up a random number generator for the sessionids.
*
* By preference, use a SecureRandom but allow to be injected.
*/
public void initRandom ()
{
if (_random==null)
{
try
{
_random=new SecureRandom();
}
catch (Exception e)
{
LOG.warn("Could not generate SecureRandom for session-id randomness",e);
_random=new Random();
_weakRandom=true;
}
}
else
_random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
}
/* ------------------------------------------------------------ */
/** Get the session ID with any worker ID.
*
* @param clusterId the cluster id
* @param request the request
* @return sessionId plus any worker ID.
*/
@Override
public String getExtendedId(String clusterId, HttpServletRequest request)
{
if (_workerName!=null)
{
if (_workerAttr==null)
return clusterId+'.'+_workerName;
String worker=(String)request.getAttribute(_workerAttr);
if (worker!=null)
return clusterId+'.'+worker;
}
return clusterId;
}
/* ------------------------------------------------------------ */
/** Get the session ID without any worker ID.
*
* @param extendedId the session id with the worker extension
* @return sessionId without any worker ID.
*/
@Override
public String getId(String extendedId)
{
int dot=extendedId.lastIndexOf('.');
return (dot>0)?extendedId.substring(0,dot):extendedId;
}
/* ------------------------------------------------------------ */
/**
* Remove an id from use by telling all contexts to remove a session with this id.
*
* @see org.eclipse.jetty.server.SessionIdManager#expireAll(java.lang.String)
*/
@Override
public void expireAll(String id)
{
//take the id out of the list of known sessionids for this node
if (removeId(id))
{
//tell all contexts that may have a session object with this id to
//get rid of them
for (SessionManager manager:getSessionManagers())
{
manager.invalidate(id);
}
}
}
/* ------------------------------------------------------------ */
/**
* @param id
*/
public void invalidateAll (String id)
{
//take the id out of the list of known sessionids for this node
if (removeId(id))
{
//tell all contexts that may have a session object with this id to
//get rid of them
for (SessionManager manager:getSessionManagers())
{
manager.invalidate(id);
}
}
}
/* ------------------------------------------------------------ */
/** Generate a new id for a session and update across
* all SessionManagers.
*
* @see org.eclipse.jetty.server.SessionIdManager#renewSessionId(java.lang.String, java.lang.String, javax.servlet.http.HttpServletRequest)
*/
@Override
public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
{
//generate a new id
String newClusterId = newSessionId(request.hashCode());
removeId(oldClusterId);//remove the old one from the list
//tell all contexts to update the id
for (SessionManager manager:getSessionManagers())
{
manager.renewSessionId(oldClusterId, oldNodeId, newClusterId, getExtendedId(newClusterId, request));
}
}
/* ------------------------------------------------------------ */
/** Get SessionManager for every context.
*
* @return
*/
protected Set<SessionManager> getSessionManagers()
{
Set<SessionManager> managers = new HashSet<>();
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
{
SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = (SessionManager)sessionHandler.getSessionManager();
if (manager != null)
managers.add(manager);
}
}
return managers;
}
}