View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server.session;
20  
21  import java.security.SecureRandom;
22  import java.util.Random;
23  
24  import javax.servlet.http.HttpServletRequest;
25  
26  import org.eclipse.jetty.server.SessionIdManager;
27  import org.eclipse.jetty.util.component.AbstractLifeCycle;
28  import org.eclipse.jetty.util.log.Log;
29  import org.eclipse.jetty.util.log.Logger;
30  
31  public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager
32  {
33      private static final Logger LOG = Log.getLogger(AbstractSessionIdManager.class);
34  
35      private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
36  
37      protected Random _random;
38      protected boolean _weakRandom;
39      protected String _workerName;
40      protected String _workerAttr;
41      protected long _reseed=100000L;
42  
43      /* ------------------------------------------------------------ */
44      public AbstractSessionIdManager()
45      {
46      }
47  
48      /* ------------------------------------------------------------ */
49      public AbstractSessionIdManager(Random random)
50      {
51          _random=random;
52      }
53  
54  
55      /* ------------------------------------------------------------ */
56      /**
57       * Get the workname. If set, the workername is dot appended to the session
58       * ID and can be used to assist session affinity in a load balancer.
59       *
60       * @return String or null
61       */
62      @Override
63      public String getWorkerName()
64      {
65          return _workerName;
66      }
67  
68      /* ------------------------------------------------------------ */
69      /**
70       * Set the workername. If set, the workername is dot appended to the session
71       * ID and can be used to assist session affinity in a load balancer.
72       * A worker name starting with $ is used as a request attribute name to
73       * lookup the worker name that can be dynamically set by a request
74       * Customizer.
75       *
76       * @param workerName the name of the worker
77       */
78      public void setWorkerName(String workerName)
79      {
80          if (isRunning())
81              throw new IllegalStateException(getState());
82          if (workerName.contains("."))
83              throw new IllegalArgumentException("Name cannot contain '.'");
84          _workerName=workerName;
85      }
86  
87      /* ------------------------------------------------------------ */
88      public Random getRandom()
89      {
90          return _random;
91      }
92  
93      /* ------------------------------------------------------------ */
94      public synchronized void setRandom(Random random)
95      {
96          _random=random;
97          _weakRandom=false;
98      }
99  
100     /* ------------------------------------------------------------ */
101     /**
102      * @return the reseed probability
103      */
104     public long getReseed()
105     {
106         return _reseed;
107     }
108 
109     /* ------------------------------------------------------------ */
110     /** Set the reseed probability.
111      * @param reseed  If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded.
112      */
113     public void setReseed(long reseed)
114     {
115         _reseed = reseed;
116     }
117 
118     /* ------------------------------------------------------------ */
119     /**
120      * Create a new session id if necessary.
121      *
122      * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
123      */
124     @Override
125     public String newSessionId(HttpServletRequest request, long created)
126     {
127         synchronized (this)
128         {
129             if (request==null)
130                 return newSessionId(created);
131 
132             // A requested session ID can only be used if it is in use already.
133             String requested_id=request.getRequestedSessionId();
134             if (requested_id!=null)
135             {
136                 String cluster_id=getClusterId(requested_id);
137                 if (idInUse(cluster_id))
138                     return cluster_id;
139             }
140 
141             // Else reuse any new session ID already defined for this request.
142             String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
143             if (new_id!=null&&idInUse(new_id))
144                 return new_id;
145 
146             // pick a new unique ID!
147             String id = newSessionId(request.hashCode());
148 
149             request.setAttribute(__NEW_SESSION_ID,id);
150             return id;
151         }
152     }
153 
154     /* ------------------------------------------------------------ */
155     public String newSessionId(long seedTerm)
156     {
157         // pick a new unique ID!
158         String id=null;
159         while (id==null||id.length()==0||idInUse(id))
160         {
161             long r0=_weakRandom
162                     ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
163                     :_random.nextLong();
164             if (r0<0)
165                 r0=-r0;
166                     
167             // random chance to reseed
168             if (_reseed>0 && (r0%_reseed)== 1L)
169             {
170                 if (LOG.isDebugEnabled())
171                     LOG.debug("Reseeding {}",this);
172                 if (_random instanceof SecureRandom)
173                 {
174                     SecureRandom secure = (SecureRandom)_random;
175                     secure.setSeed(secure.generateSeed(8));
176                 }
177                 else
178                 {
179                     _random.setSeed(_random.nextLong()^System.currentTimeMillis()^seedTerm^Runtime.getRuntime().freeMemory());
180                 }
181             }
182             
183             long r1=_weakRandom
184                 ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
185                 :_random.nextLong();
186             if (r1<0)
187                 r1=-r1;
188             
189             id=Long.toString(r0,36)+Long.toString(r1,36);
190 
191             //add in the id of the node to ensure unique id across cluster
192             //NOTE this is different to the node suffix which denotes which node the request was received on
193             if (_workerName!=null)
194                 id=_workerName + id;
195     
196         }
197         return id;
198     }
199 
200 
201     /* ------------------------------------------------------------ */
202     @Override
203     public abstract void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);
204 
205     
206     /* ------------------------------------------------------------ */
207     @Override
208     protected void doStart() throws Exception
209     {
210        initRandom();
211        _workerAttr=(_workerName!=null && _workerName.startsWith("$"))?_workerName.substring(1):null;
212     }
213 
214     /* ------------------------------------------------------------ */
215     @Override
216     protected void doStop() throws Exception
217     {
218     }
219 
220     /* ------------------------------------------------------------ */
221     /**
222      * Set up a random number generator for the sessionids.
223      *
224      * By preference, use a SecureRandom but allow to be injected.
225      */
226     public void initRandom ()
227     {
228         if (_random==null)
229         {
230             try
231             {
232                 _random=new SecureRandom();
233             }
234             catch (Exception e)
235             {
236                 LOG.warn("Could not generate SecureRandom for session-id randomness",e);
237                 _random=new Random();
238                 _weakRandom=true;
239             }
240         }
241         else
242             _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
243     }
244 
245     /** Get the session ID with any worker ID.
246      *
247      * @param clusterId the cluster id
248      * @param request the request
249      * @return sessionId plus any worker ID.
250      */
251     @Override
252     public String getNodeId(String clusterId, HttpServletRequest request)
253     {
254         if (_workerName!=null)
255         {
256             if (_workerAttr==null)
257                 return clusterId+'.'+_workerName;
258 
259             String worker=(String)request.getAttribute(_workerAttr);
260             if (worker!=null)
261                 return clusterId+'.'+worker;
262         }
263     
264         return clusterId;
265     }
266 
267     /** Get the session ID without any worker ID.
268      *
269      * @param nodeId the node id
270      * @return sessionId without any worker ID.
271      */
272     @Override
273     public String getClusterId(String nodeId)
274     {
275         int dot=nodeId.lastIndexOf('.');
276         return (dot>0)?nodeId.substring(0,dot):nodeId;
277     }
278 
279 
280 }