http是无状态的协议,在web应用中通过cookie和session来实现会话。cookie存储在客户端,session存储在服务端。
cookie缺点:
-
cookie管理的混乱。在大型互联网应用系统中,如果每个应用系统都管理每个应用使用的cookie,将会导致混乱,由于通常应用系统都在同一个根域名下面,cookie又会存在数量和容量的问题,所以如果没有统一的管理规范很容易出现cookie超出限制的情况。
-
安全问题。虽然可以通过设置httponly属性防止一些私密cookie被客户端访问,但是仍然不能保证cookie无法被篡改。为了保证cookie的私密性,通常会对cookie进行加密,但是维护加密key也是一件比较麻烦的事情,无法保证定期来更新加密key也是带来安全隐患的一个重要因素。
-
浏览器每次请求会根据domain和path信息过滤并带上全量的cookie,如果cookie很多,会造成数据量传输很大,可以通过存session解决。
cookie
cookie技术是客户端(浏览器)的解决方案。cookie是由服务器发给客户端的特殊信息,以文本文件的方式存储在客户端,当客户端每次发起请求时,会带上这些信息,标识客户端的状态。
举个 :当用户使用浏览器访问一个支持Cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器;接着,服务器在向客户端回传相应的超文本的同时也会发回这些个人信息,当然这些信息并不是存放在HTTP响应体(Response Body)中的,而是存放于HTTP响应头(Response Header);
image.png
当客户端浏览器接收到来自服务器的响应之后,浏览器会将这些信息存放在
一个统一的位置,对于Windows操作系统而言,我们可以从:[系统盘]:\Documents and Settings\[用户名]\Cookies
目录中找到存储的Cookie;自此,客户端再向服务器发送请求的时候,都会把相应的Cookie再次发回至服务器。而这次,Cookie信息则存放在HTTP请求头(Request Header)了。
image.png
有了Cookie这样的技术实现,服务器在接收到来自客户端浏览器的请求之后,就能够通过分析存放于请求头的Cookie得到客户端特有的信息,从而动态生成与该客户端相对应的内容。通常,我们可以从很多网站的登录界面中看到“请记住我”这样的选项,如果你勾选了它之后再登录,那么在下一次访问该网站的时候就不需要进行重复而繁琐的登录动作了,而这个功能就是通过Cookie实现的。
- cookie创建
web服务器通过HTTP请求头Set-Cookie指定要存储的cookie值。Set-Cookie请求头格式如下所示(方括号里的部分是可选的):
Set-Cookie: value[; expires=data][; domain=domain][; path=path][; secure]
- 过期选项
cookie之后的选项使用分号加空格分开,这些选项指定了cookie传送回服务器时需要遵守的规则。第一个选项为expires,指明了cookie不再传回服务器,且浏览器应该删除cookie的时间。expires选项值的格式为日期格式(Wdy,DD-Mon-YYYY HH:MM:SS GMT),例如:
Set-Cookie: name=Nicholas; expires=Sat, 02 May 2009 23:38:25 GMT
如果没有expires选项的话,则cookie过期时间为session过期时间。session在浏览器关闭时过期,因此session cookie仅在浏览器打开时存在。这也是为什么很多网站在登陆时都会提供“保持登陆”选项的单选框。该单选框被选中时,将会给登陆cookie加上一个expires值。如果expires选项被设为过去的日期,则cookie会被马上删除掉。
- domain(域)选项
下一个选项为domain
,指定了cookie将被发送到的域名。默认情况下,domain
被设置为设置cookie页面的主机名,这样可以在向相同域名发送请求时传递cookie值。例如,原文所在页面cookie的默认域名会被设为www.nczonline.net
。domain
选项用于扩大cookie值将会被发送到的域名数量。例如:
Set-Cookie: name=Nicholas; domain=nczonline.net
考虑下Yahoo!这样拥有许多name.yahoo.com
等子域名的大型网站(例如
my.yahoo.com
,finance.yahoo.com
等等)。可以为所有这些站点设置一个cookie值,只需要将domain
选项设置成yahoo.com
。浏览器会从后往前检查请求发送的host值(主机名)和cookie的domain值,并在找到匹配时发送相应的Cookie
头信息。
domain
选项值必须是Set-Cookie
头信息发送到的主机名的一部分。例如,在淘宝站点上不能设置cookie的domain为google.com,因为这样会引发安全问题。无效的domain选项会被忽略。
- path(路径)选项
另一种控制Cookie头信息发送时间的方法是指定path选项。与domain选项类似,path选项指定了在发送Cookie头信息之前被请求资源中必须存在的URL路径信息。这种比较是通过从请求URL字符串开始一个字符一个字符与选项值进行对比完成的。如果字符串匹配的话,就会发送Cookie头信息。例如:
Set-Cookie: name=Nicholas; path=/blog
在上面的例子中,path选项会匹配到/blog,/blogroll这样的路径,所有以/blog开头的路径都是有效的。注意:这种比较匹配只会有一次,domain选项匹配生效即停止。path选项的默认值是发送Set-Cookie头信息的URL路径地址。
- secure(安全)选项
最后一个选项是secure。与其他选项不同,这个选项是一个标记值,没有指定多余的值。有secure标记的cookie只能通过SSL和HTTPS协议传送。例如:
Set-Cookie: name=Nicholas; secure
实际中,机密或者敏感信息都不应该存储在cookie里或者使用cookie来传递,因为整个
cookie机制实际上是天生不安全的。默认情况下,通过HTTPS链接传输的cookie值都会被自动设为secure。
session
session技术是服务器端的解决方案。session是由服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在内存、数据库、文件中,通过session数据来保持http状态。
举个 :cookie可以让服务端程序跟踪到每个客户端浏览器的访问,但是每次客户端的访问都必须传回这些cookie,如果cookie很多,这无形中增加了客户端与服务器端的数据传输量,而session的出现正是来解决这个问题的。
基于session的解决方案是同一个客户端每次和服务器交互时,不需要每次都传回所有的cookie值,而是只要传回一个id,这个id是客户端第一次访问服务器的时候产生的,而且每个客户端是唯一的,这样每个客户端就都拥有一个唯一的id值,客户端只要传回这个id就行了,例如首次访问www.baidu.com,客户端请求服务器,服务器收到请求把id值set到cookie中
image.png
再次发送请求的时候,就把这个id回传给服务器用以跟踪用户状态。
image.png
- session工作的三种方式
-
基于URL Path Paramter,默认支持。可以参考关于session的实现:cookie与url重写
-
基于cookie,如果没有修改context容器的cookies标识,默认也是支持的
-
基于SSL,默认不支持,只有connector.getAttribute("SSLEnabled")为TRUE时才支持。
- sessionid的生成算法
以tomcat为例,查看tomcat的sessionid生成源码发现,tomcat的sessionid值生成的机制是一个随机数加时间加上jvm的id值,jvm的id值会根据服务器的硬件信息计算得来,因此不同jvm的id值都是唯一的。
public String generateSessionId() {
byte random[] = new byte[16];
// Render the result as a String of hexadecimal digits
StringBuilder buffer = new StringBuilder();
int resultLenBytes = 0;
while (resultLenBytes < sessionIdLength) {
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 (jvmRoute != null && jvmRoute.length() > 0) {
buffer.append('.').append(jvmRoute);
}
return buffer.toString();
}
private void getRandomBytes(byte bytes[]) {
SecureRandom random = randoms.poll();
if (random == null) {
random = createSecureRandom();
}
random.nextBytes(bytes);
randoms.add(random);
}
/**
* Create a new random number generator instance we should use for
* generating session identifiers.
*/
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.newInstance();
} catch (Exception e) {
log.error(sm.getString("sessionIdGenerator.random",
secureRandomClass), e);
}
}
if (result == null) {
// No secureRandomClass or creation failed. Use SecureRandom.
try {
if (secureRandomProvider != null &&
secureRandomProvider.length() > 0) {
result = SecureRandom.getInstance(secureRandomAlgorithm,
secureRandomProvider);
} else if (secureRandomAlgorithm != null &&
secureRandomAlgorithm.length() > 0) {
result = SecureRandom.getInstance(secureRandomAlgorithm);
}
} catch (NoSuchAlgorithmException e) {
log.error(sm.getString("sessionIdGenerator.randomAlgorithm",
secureRandomAlgorithm), e);
} catch (NoSuchProviderException e) {
log.error(sm.getString("sessionIdGenerator.randomProvider",
secureRandomProvider), e);
}
}
if (result == null) {
// Invalid provider / algorithm
try {
result = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
log.error(sm.getString("sessionIdGenerator.randomAlgorithm",
secureRandomAlgorithm), e);
}
}
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.info(sm.getString("sessionIdGenerator.createRandom",
result.getAlgorithm(), Long.valueOf(t2-t1)));
return result;
}
网友评论