0%

JsessionId的生成

前言

因为http是无状态的,所以产生了cookie,session机制来构造一个会话。

一般流程是这样:

  • 客户端发送请求
  • 服务端接收请求,并且设置Set-Cookie头,分配一个sessionId
  • 客户端存在cookie,下次发送时带上sessionid
  • 服务端解析sessionid,找到对应的session信息

所以我们可以知道,核心就在这个sessionid身上。

那么这个sessionid需要哪些特质呢

  • 唯一性:
    这个是肯定的,万一前一个人请求,发送sessionid,后面紧接着又来一个人,发送一个sessionid,两个人的id重合了,那就出问题了。

  • 不容易猜到性:
    这个其实是阻拦一些攻击的。
    java中自带了UUID

    1
    UUID.randomUUID().toString().replaceAll("-", "");

    这样做其实可以满足唯一性,但是使用UUID的一个问题是,UUID其实并不是那么不好猜。
    因为UUID的使用领域就不是为了安全领域的。
    参见StackOverFlow

    https://stackoverflow.com/questions/5244455/best-practices-for-sessionid-authentication-token-generation

Tomcat的实现

由于本人算法渣,自己想不到什么好办法实现,所以就去看了Tomcat怎么实现。

SecureRandom

1
2
3
4
5
6
//SecureRandom是在java.security包中
//主要是用来产生随机数
//这个类主要依赖sun.security.provider.SecureRandom()我是没看明白。。。
//下面的sessionid的实现主要也是利用这个类实现的
public class SecureRandom extends java.util.Random {
}

SessionIdGenerator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface SessionIdGenerator {
//这个参数和集群有关,下面会看到
public String getJvmRoute();
public void setJvmRoute(String jvmRoute);

//sessionid的长度
public int getSessionIdLength();
//设置sessionid的长度
public void setSessionIdLength(int sessionIdLength);

//这是最核心的方法,下面会看到他的实现
public String generateSessionId();

public String generateSessionId(String route);
}

SessionIdGeneratorBase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public abstract class SessionIdGeneratorBase extends LifecycleBase
implements SessionIdGenerator {

//这里保存了很多的SecureRandom实例,因为单个的SecureRandom使用了同步
//这样如果队列为空,就创建一个SecureRandom实例,这样会快点
private final Queue<SecureRandom> randoms = new ConcurrentLinkedQueue<>();

private String secureRandomClass = null;

private String secureRandomAlgorithm = "SHA1PRNG";

private String secureRandomProvider = null;

//这个参数很有意思,如果是单机上的,就是"",如果是在tomcat集群中,那么这个就是集群的编号
private String jvmRoute = "";

//默认长度是16
private int sessionIdLength = 16;


//产生一个sessionid
//如果是单机的话,就是调用了
//generateSessionId("")
public String generateSessionId() {
return generateSessionId(jvmRoute);
}


//利用queue中的SecureRandom产生随机数
//这里可以看到为什么要用一个队列存SecureRandom实例了。
protected void getRandomBytes(byte bytes[]) {
SecureRandom random = randoms.poll();
if (random == null) {
random = createSecureRandom();
}
random.nextBytes(bytes);
randoms.add(random);
}


//这里产生一个SecureRandom实例,一般来说会从找是否设置了
//自定义的SecureRandom,如果没有,就创建一个系统的
private SecureRandom createSecureRandom() {

SecureRandom result = null;

long t1 = System.currentTimeMillis();
if (secureRandomClass != null) {
try {
// Construct and seed a new random number generator
Class<?> clazz = Class.forName(secureRandomClass);
result = (SecureRandom) clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(sm.getString("sessionIdGeneratorBase.random",
secureRandomClass), e);
}
}

boolean error = false;
if (result == null) {
...
}

if (result == null && error) {
....
}

//用系统的
if (result == null) {
// Nothing works - use platform default
result = new SecureRandom();
}

// Force seeding to take place
result.nextInt();

long t2 = System.currentTimeMillis();
if ((t2 - t1) > 100) {
log.warn(sm.getString("sessionIdGeneratorBase.createRandom",
result.getAlgorithm(), Long.valueOf(t2 - t1)));
}
return result;
}
}

StandardSessionIdGenerator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//这个类只重写了一个generateSessionId方法
public class StandardSessionIdGenerator extends SessionIdGeneratorBase {


public String generateSessionId(String route) {

byte random[] = new byte[16];
int sessionIdLength = getSessionIdLength();

StringBuilder buffer = new StringBuilder(2 * sessionIdLength + 20);

int resultLenBytes = 0;

while (resultLenBytes < sessionIdLength) {
//这个方法就是利用SecureRandom得到随机数
getRandomBytes(random);
for (int j = 0;
j < random.length && resultLenBytes < sessionIdLength;
j++) {
//对产生的随机数进行一些处理
byte b1 = (byte) ((random[j] & 0xf0) >> 4);
byte b2 = (byte) (random[j] & 0x0f);
if (b1 < 10)
buffer.append((char) ('0' + b1));
else
buffer.append((char) ('A' + (b1 - 10)));
if (b2 < 10)
buffer.append((char) ('0' + b2));
else
buffer.append((char) ('A' + (b2 - 10)));
resultLenBytes++;
}
}

//加上集群中的本机编号
if (route != null && route.length() > 0) {
buffer.append('.').append(route);
} else {
String jvmRoute = getJvmRoute();
if (jvmRoute != null && jvmRoute.length() > 0) {
buffer.append('.').append(jvmRoute);
}
}

return buffer.toString();
}
}

总结

总的来说

  • Tomcat的sessionid的生成主要是随机数,依赖的类是java.security.SecureRandom
  • 为了避免多线程竞争的问题,引入了一个queue,当SecureRandom不够用就生成一个。
  • 同时在集群中加入了本机的编号