View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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 workname. 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       * customiser.
75       *
76       * @param workerName
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                 LOG.debug("Reseeding {}",this);
171                 if (_random instanceof SecureRandom)
172                 {
173                     SecureRandom secure = (SecureRandom)_random;
174                     secure.setSeed(secure.generateSeed(8));
175                 }
176                 else
177                 {
178                     _random.setSeed(_random.nextLong()^System.currentTimeMillis()^seedTerm^Runtime.getRuntime().freeMemory());
179                 }
180             }
181             
182             long r1=_weakRandom
183                 ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
184                 :_random.nextLong();
185             if (r1<0)
186                 r1=-r1;
187             
188             id=Long.toString(r0,36)+Long.toString(r1,36);
189 
190             //add in the id of the node to ensure unique id across cluster
191             //NOTE this is different to the node suffix which denotes which node the request was received on
192             if (_workerName!=null)
193                 id=_workerName + id;
194     
195         }
196         return id;
197     }
198 
199 
200     /* ------------------------------------------------------------ */
201     @Override
202     public abstract void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);
203 
204     
205     /* ------------------------------------------------------------ */
206     @Override
207     protected void doStart() throws Exception
208     {
209        initRandom();
210        _workerAttr=(_workerName!=null && _workerName.startsWith("$"))?_workerName.substring(1):null;
211     }
212 
213     /* ------------------------------------------------------------ */
214     @Override
215     protected void doStop() throws Exception
216     {
217     }
218 
219     /* ------------------------------------------------------------ */
220     /**
221      * Set up a random number generator for the sessionids.
222      *
223      * By preference, use a SecureRandom but allow to be injected.
224      */
225     public void initRandom ()
226     {
227         if (_random==null)
228         {
229             try
230             {
231                 _random=new SecureRandom();
232             }
233             catch (Exception e)
234             {
235                 LOG.warn("Could not generate SecureRandom for session-id randomness",e);
236                 _random=new Random();
237                 _weakRandom=true;
238             }
239         }
240         else
241             _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
242     }
243 
244     /** Get the session ID with any worker ID.
245      *
246      * @param clusterId
247      * @param request
248      * @return sessionId plus any worker ID.
249      */
250     @Override
251     public String getNodeId(String clusterId, HttpServletRequest request)
252     {
253         if (_workerName!=null)
254         {
255             if (_workerAttr==null)
256                 return clusterId+'.'+_workerName;
257 
258             String worker=(String)request.getAttribute(_workerAttr);
259             if (worker!=null)
260                 return clusterId+'.'+worker;
261         }
262     
263         return clusterId;
264     }
265 
266     /** Get the session ID without any worker ID.
267      *
268      * @param nodeId the node id
269      * @return sessionId without any worker ID.
270      */
271     @Override
272     public String getClusterId(String nodeId)
273     {
274         int dot=nodeId.lastIndexOf('.');
275         return (dot>0)?nodeId.substring(0,dot):nodeId;
276     }
277 
278 
279 }