coding……
但行好事 莫问前程

了解Cookie、Session和Token

在Web刚兴起的阶段,Web服务都是静态服务,一般处理前端请求,只需要将相应的html、图片等文件传送到前端即可,这个时候,对于同一个请求,每个用户看到的内容都是完全一样的,服务器也没必要针对不同用户做不同的处理。Http协议最开始就是定义服务器和客户端传送超文本文件的协议,从起源上看,Http协议就是无状态的。

但是随着交互式Web应用的兴起,之前无状态的Http协议就不能满足了。比如购物、论坛等场景,服务器需要知道当前请求是否已登陆、用户在购物车添加了那些商品,针对不同的请求,要做不同的处理,之前无状态很明显不能满足这种需求了。

解决方案就是给每个客户端颁发一个会话标识(session id),说白了就是一个随机的字符串,每个客户端收到的都不一样,每次客户端向服务端发起HTTP请求时,会把这个字符串给一并带过来, 这样服务端就能区分开谁是谁了。而本文讲的Cookie、Session和Token就是用来解决Http无状态的不同方式。

1. Cookie

1.1 基本概念

上面讲到,为了服务端能区分不同的客户端请求,会给每一个客户端颁发一个会话标识(Session Id)。这个会话标识有很多方式可以发送给客户端,而Cookie就是其中一种比较常见的方式。

Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式(存储session id)。Cookie总是保存在客户端中,按在客户端中的存储位置,可分为内存Cookie和硬盘Cookie。内存Cookie由浏览器维护,保存在内存中,浏览器关闭后就消失了,其存在时间是短暂的。硬盘Cookie保存在硬盘里,有一个过期时间,除非用户手工清理或到了过期时间,硬盘Cookie不会被删除,其存在时间是长期的。内存Cookie和硬盘Cookie也可以叫做非持久Cookie和持久Cookie。

Cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用Cookie实际上只能存储一小段的文本信息。例如:输入用户名密码登录网站后,第二天再打开很多情况下就直接打开了,这个时候用到的一个机制就是Cookie。

1.2 Cookie的缺陷

  • Cookie会被附加在每个HTTP请求中,所以无形中增加了流量。
  • 由于在HTTP请求中的Cookie是明文传递的,所以安全性成问题,除非用HTTPS
  • Cookie的大小限制在4KB左右,对于复杂的存储需求来说是不够用的。
  • Cookie有可能会被XSS攻击获取,如果Cookie中存储的是用户的SessionId,那么攻击者就可以通过合法身份操作用户账户,有一定的安全隐患。

2. Session

2.1 基本概念

上面讲到为了使服务器可以区分不同的客户端的请求,可以给每个客户端一个独立的会话标识(Session Id)方式来实现。那么仔细想一下,如果只使用这个sessionId能不能达到目的?比如如果服务端没有记录这个Session Id对应的信息,服务端怎么知道这个Session Id是自己颁发的?假如服务端不额外记录任何信息,我们想象一下登陆的场景,如果想实现下次用户访问不用重新登陆,那必然要把用户信息的信息(用户名,密码)存储在Cookie中传给客户端,然后客户端每次请求把Cookie带给服务端,服务端在用户不感知的情况下去校验,但上面也讲过Cookie中存储密码等敏感信息是不合理的。

所以到这里,我们可以得出一个结论,为了使服务器可以区分不同客户端的请求,服务端必须额外也维护一些信息,这个信息跟颁发给客户端的标识(Session Id)是一一对应的。而服务端额外维护的信息就是Session。

Session在服务端保存一个数据结构(主要存储Session Id和Session内容,同时也包含了很多自定义的内容如:用户基础信息、权限信息),这个数据可以保存在内存、数据库、文件中,Redis等介质中。当客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在上述介质中,并把Session Id通过Cookie给回客户端。客户端浏览器再次访问时只需将Cookie中的SessionId给回服务端,服务端就可以根据Session Id和自己存储的Session信息,来确定客户端身份了。

比如用户第一次登录后,浏览器会将用户信息发送给服务器,服务器校验通过后,会为该用户创建一个SessionId,并在响应内容(Cookie)中将该Session Id一并返回给浏览器,浏览器将这些数据保存在本地。当用户再次发送请求时,浏览器会自动的把上次请求存储的Cookie数据自动的携带给服务器。服务器接收到请求信息后,会通过浏览器请求的数据中的Session Id判断当前是哪个用户,然后根据Session Id在Session库中获取用户的Session数据返回给浏览器。

Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。一般服务器会给Session维护一个超时时间,过了超时时间,服务器就会把这个Session删除。如果过了超时时间再访问过服务器,Session就自动失效了。

2.1 Session的缺陷

在用户量比较少时,上述这种维护用户状态的方式是没什么问题的。但是用户量一旦达到一定规模,就会对服务起产生比较大的压力。比如服务起将Session存储在内存中,用户量达到千万级别,服务起就要维护千万个用户的Session,这对服务起而言本来就是一个比较大的挑战。

除此之外,Session的存在,也限制了服务器的水平扩展能力。比如用户规模变大后,服务端做水平扩展,新增了几台服务实例。此时用户1通过服务器A登陆了系统,那么用户1的Session就会保存在机器A上,假如用户1下一次的请求被转发到机器B上,就会出现问题,因为机器B上没有用户1的Session。

针对上述这个问题,也有解决方案,比如session sticky。就是通过负载均衡服务器的策略,将用户1的请求只转发到机器A上,就能解决上述问题了。但是也会引入一个新的问题,对于单个用户而言,一旦处理该用户的服务器挂了,还是要将用户请求转发到其他机器上,还是会出现Session丢失的问题。所以为了彻底解决这个问题,就只能在不同服务器之间进行Session复制,那么又回到最开始的状态了,每个服务器都要维护大量的Session,对服务器而言是个很大的负担。

如果不把Session保存在内存,而借助Redis、Memcached等统一管理,也能解决Session复制问题,如下:

但是,引入Redis、Memcached也会出现另一个问题,就是存储介质的单点问题,如果Redis或者Memcached挂了,那么所有的Session都会丢失。虽然可以使用存储介质集群化的方式,提高可用行,但是额外维护Session,对于服务端而言确实是一个负担。

3. Token

3.1 基本概念

上面讲Session时讲到,服务端单独存储Session,对服务端而言是一个负担。那有没有一种方式可以让服务端不用单独维护Session?在上面也讲过,如果服务端不维护Session,那么就无法确认客户端带过来的Session Id是否是自己颁发的。这样不怀好意的人伪造一个Session Id给服务器,服务器也是无法辨别的。那有没有一种方式,可以在服务端不额外维护Session的前提下,识别客户端送过来的校验信息的合法性?(比如是否是自己颁发的,有没有被恶意改动过)答案是有的,就是接下来要讲的Token。

比如用户1已经登录了系统, 服务器会给他发一个令牌(Token), 里边包含了用户1的user id, 下一次用户1再次请求服务器的时候, 把这个Token通过Http header 带给服务器就可以了。那么服务器是如何验证Token的合法性的,我们来看一下Token的生成规则。

首先对要传送到客户端的数据做一个签名,比如使用HMAC-SHA256算法,加上一个只有我才知道的密钥,对数据做一个签名, 把这个签名和数据一起作为Token,由于密钥别人不知道,就无法伪造Token了。

这个Token服务端并不保存,用户1把这个Token发给服务器的时候,服务器再用同样的HMAC-SHA256算法和密钥,对数据再计算一次签名, 和Token中的签名做个比较, 如果相同,服务器就知道用户1已经登录过了,并且可以直接取到用户1的user id , 如果不相同,数据部分肯定被人篡改过,直接拒绝服务。

Token中的数据是明文保存的(虽然会用Base64做下编码,但那不是加密),还是可以被别人看到的,所以不能在其中保存像密码这样的敏感信息。另外,如果一个人的Token被别人偷走了,那服务器也会认为小偷就是合法用户, 这其实和一个人的Session id被别人偷走是一样的。

这样一来,服务器就不用额外保存Session了, 只需要生成Token,然后验证Token,相当于用CPU计算时间换取了Session存储空间。

参考链接:

1. 《码农翻身——干掉状态》

2. 维基百科——Cookie

赞(4) 打赏
Zhuoli's Blog » 了解Cookie、Session和Token
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址