发展
- 以前的web很单一,就是用来浏览文档,服务器也不需要记住谁在某个时间段浏览了什么文档,每一次请求都是一个新的http协议,就是请求加响应,不需要记住是谁发了http请求。
- 随着交互式web的兴起,比如需要登录的网站、在线购物等等,就面临了一个问题,就是要管理会话,必须要记住哪些人登录系统,哪些人往自己的购物车中放商品,也就是说要把每个人区分开。http请求是无状态的,于是服务器需要给大家发一个会话标识(session id),就是一串随机的字符串。由于每个人收到的都不一样,这时候再向服务端发送http请求时,一并把session id带过来,这样服务端就能区分谁是谁了。
- 随之而来的问题就是,每个人只需要保存自己的session id,但是服务器需要保存所有的session id。如果用户多了,对于服务器是一个很大的负担,严重限制了服务器的扩展能力。比如说,服务端由机器A和机器B构成一个集群,小明同学的第一次请求被转发到了机器A,那么机器A就会记录下session id,但是第二次请求被转发到了机器B上,此时机器B并没有小明同学的session id。
当然可以使用session sticky(会话粘连),让小明同学的请求一直转发到机器A上。但是如果机器A挂了,那么请求还是会转发到机器B上。那么只能去做session复制了,将session id在两台机器上搬来搬去。
于是再借助Memcached,将session id集中存储到一个地方,所有的机器都来访问存在此处的session。虽然不用复制,但是增加了单点失败的可能性。服务负责session存储的服务挂了,那么所有用户都要重新登录。我们当然可以把这个单点机器也扩展成集群,来增加可靠性,但是对于服务端始终是个负担。
- 那么,有没有办法不让服务端保存session id,只让客户端自己保存?
如果服务端不保存session id,又怎么知道客户端发送的session id是服务端生成的,而不是非法用户伪造的?
譬如,小明登录了系统,系统给小明发一个令牌(token),里面包含了小明的user id;小明再请求时,把token通过http header带过来。这似乎和session id没什么区别,还是可以伪造,那么怎么做才能让别人伪造不了?
可以对数据做一个签名,也就是加密,比如用HMAC-SHA256算法,加上一个只有服务端知道的秘钥,对数据进行一次加密;再把这个签名和数据一起作为token,由于秘钥别人不知道,就无法伪造token了。
当小明把这个token发给服务端时,服务端不会去保存,只需要使用同样的加密算法和秘钥,再计算一个签名,再和小明发送过来的签名进行比较,如果相同,说明小明是合法用户。如果不相同,说明为非法用户,就提示认证失败。
token中的数据是明文保存的,虽然会做base64编码,但并不是加密。也就是说如果token被人偷走了,那么也只能任务小偷是合法用户,这和session id被人偷走也是一个道理。
如此这般,服务端就不需要保存session id,只是生成token,验证token。时间换空间,用cpu计算时间换取session存储空间。
cookie
是什么
Cookie翻译成中文的意思是‘小甜饼’,是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。
Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。Cookie存储的数据量有限,且都是保存在客户端浏览器中。不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用Cookie实际上只能存储一小段的文本信息(key-value格式)。
机制
当用户第一次访问并登录一个网站的时候,cookie的设置以及发送会经历以下4个步骤:
客户端发送一个请求到服务器;
服务器发送一个HttpResponse响应到客户端,其中包含Set-Cookie的头部;
客户端保存cookie,之后向服务器发送请求时,HttpRequest请求中会包含一个Cookie的头部;
服务器返回响应数据。
为了探究这个过程,写了代码进行测试,如下:
我在doGet方法中,new了一个Cookie对象并将其加入到了HttpResponse对象中
浏览器输入地址进行访问,结果如图所示:
可见Response Headers中包含Set-Cookie头部,而Request Headers中包含了Cookie头部。name和value正是上述设置的。
属性
- Expires
该属性用来设置Cookie的有效期。Cookie中的maxAge用来表示该属性,单位为秒。Cookie中通过getMaxAge()和setMaxAge(int maxAge)来读写该属性。maxAge有3种值,分别为正数,负数和0。
如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的Cookie持久化,即写到对应的Cookie文件中(每个浏览器存储的位置不一致)。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时该Cookie仍然有效。下面代码中的Cookie信息将永远有效。
Cookie cookie = new Cookie("jiangwang",System.currentTimeMillis()+"");
// 设置生命周期为MAX_VALUE,永久有效
cookie.setMaxAge(Integer.MAX_VALUE);resp.addCookie(cookie);
当maxAge属性为负数,则表示该Cookie只是一个临时Cookie,不会被持久化,仅在本浏览器窗口或者本窗口打开的子窗口中有效,关闭浏览器后该Cookie立即失效。 - 修改或者删除Cookie
HttpServletResponse提供的Cookie操作只有一个addCookie(Cookie cookie),所以想要修改Cookie只能使用一个同名的Cookie来覆盖原先的Cookie。如果要删除某个Cookie,则只需要新建一个同名的Cookie,并将maxAge设置为0,并覆盖原来的Cookie即可。
新建的Cookie,除了value、maxAge之外的属性,比如name、path、domain都必须与原来的一致才能达到修改或者删除的效果。否则,浏览器将视为两个不同的Cookie不予覆盖。 - 域名
Cookie是不可以跨域名的,隐私安全机制禁止网站非法获取其他网站的Cookie。
正常情况下,同一个一级域名下的两个二级域名也不能交互使用Cookie,比如a1.jiangwang.com和a2.jiangwang.com,因为二者的域名不完全相同。如果想要jiangwnag.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数为.jiangwang.com,这样使用a1.jiangwang.com和a2.jiangwang.com就能访问同一个cookie。 - 路径
path属性决定允许访问Cookie的路径。比如,设置为"/"表示允许所有路径都可以使用Cookie
应用
Cookies最典型的应用是判定注册用户是否已经登录网站,用户可能会得到提示,是否在下一次进入此网站时保留用户信息以便简化登录手续,这些都是Cookies的功用。另一个重要应用场合是“购物车”之类处理。用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品,这些信息都会写入Cookies,以便在最后付款时提取信息。
session
是什么
在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。
原理
服务器创建session出来后,会把session的id号,以cookie的形式回写给客户机,这样,只要客户机的浏览器不关,再去访问服务器时,都会带着session的id号去,服务器发现客户机浏览器带session id过来了,就会使用内存中与之对应的session为之服务。
token
是什么
token的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。
当用户第一次登录后,服务器生成一个token并将此token返回给客户端,以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。
简单token的组成;uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token的前几位以哈希算法压缩成的一定长度的十六进制字符串,为防止token泄露)。
原理
用户通过用户名和密码发送请求→服务端校验→服务端返回一个token给客户端→客户端存储token→客户端请求时携带token→服务端验证token并返回数据
优点
- 支持跨域访问: Cookie是不允许垮域访问的,token支持;
- 无状态: token无状态,session有状态的;
- 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在 你的API被调用的时候, 你可以进行Token生成调用即可;
- 更适用于移动应用: Cookie不支持手机端访问的;
- 性能: 在网络传输的过程中,性能更好;
- 基于标准化: 你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在 多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如: Firebase,Google, Microsoft)。
缺点
- 占带宽,正常情况下要比 session_id 更大,需要消耗更多流量,挤占更多带宽,假如你的网站每月有 10 万次的浏览器,就意味着要多开销几十兆的流量。听起来并不多,但日积月累也是不小一笔开销。实际上,许多人会在 JWT 中存储的信息会更多;
- 无法在服务端注销,那么久很难解决劫持问题;
- 性能问题,JWT 的卖点之一就是加密签名,由于这个特性,接收方得以验证 JWT 是否有效且被信任。但是大多数 Web 身份认证应用中,JWT 都会被存储到 Cookie 中,这就是说你有了两个层面的签名。听着似乎很牛逼,但是没有任何优势,为此,你需要花费两倍的 CPU 开销来验证签名。对于有着严格性能要求的 Web 应用,这并不理想,尤其对于单线程环境。
参考
版权声明:本文所有权归作者! 商业用途转载请联系作者授权! 非商业用途转载,请标明本文链接及出处!
赞成、反驳、不解的小伙伴,欢迎一起交流!