前言

因为 HTTP 协议是无状态的,所以 Cookie 和 Session 都是为了在 HTTP 协议之上维护客户端和服务器之间的会话状态,从而实现处理大量事务。

Cookie 是服务器发送到客户端浏览器并保存在本地的一小块数据,内容为一系列的键值对。浏览器再次向一个 URL 发送请求时回带上 Cookie,用于告知服务器这个请求和之前的请求来自于同一个浏览器。
Cookie 主要的用途为:

  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

    因为浏览器每次发送请求都要携带 Cookie,所以会导致额外的开销(尤其是在移动端的环境下)。

分类

Cookie 根据持续的时间不同主要分为会话期 Cookie 和持久性 Cookie:

  • 会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
  • 持久性 Cookie:指定过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie。
    1
    Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;

实现流程

  1. 浏览器向服务器的某个 URL 发起 HTTP 请求(可以是任何请求,比如 GET 一个页面、POST 一个登录表单等)
  2. 对应的服务器收到 HTTP 请求,然后返回浏览器的 HTTP 响应。
  3. 服务器在响应报文中加入 Set-Cookie 字段,值就是需要设置的 Cookie。
  4. 客户端收到响应报文后将 Cookie 中的内容保存到浏览器中。Set-Cookie 的字段值可以是很多项 Cookie,每一项都会指定一个过期时间 Expires,一般默认的过期时间是用户关闭浏览器时。

    在 RFC2109 6.3 Implementation Limits 中提到: UserAgent(浏览器就是一种用户代理)至少应支持 300 项 Cookie, 每项至少应支持到 4096 字节,每个域名至少支持 20 项 Cookie。

1
2
3
4
5
6
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

[page content]
  1. 客户端再次给服务器的相同 URL 发起 HTTP 请求时,会将浏览器中的 Cookie 附加在 HTTP 请求报文的 Cookie 字段中发送给服务器。

    浏览器可以存储多个域名下的 Cookie,但是只发送当前请求的域名曾经指定的 Cookie,Set-Cookie字段中可以设置域名。

1
2
3
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
  1. 服务器收到请求报文后,发现请求头中有 Cookie 字段,可以凭借字段值判断是否访问过。

安全隐患

首先我们先分析网站使用 Cookie 标识用户登录状态的过程:

  1. 用户提交用户名和密码的表单,然后提交一个 HTTP 请求(比如 POST)。
  2. 服务器验证用户名和密码,如果合法就设置 Set-Cookie 为authed=true
  3. 浏览器存储该 Cookie 值,再次发送请求时,设置 Cookie 字段为authed=true
  4. 服务器收到第二次请求,从 Cookie 字段值发现用户已经登录过,所以按照已登陆用户的权限来处理此次请求。

仔细观察流程我们可以发现,只要将 Cookie 字段值设置为authed=true就可以欺骗服务器跳过用户登录验证步骤。一些 HTTP 客户端软件(包括 curl、Node.js)可以任意设置头字段,所以这里存在篡改 Cookie 跳过登录验证的安全隐患。

防篡改措施

可以通过为每个 Cookie 项生成签名来避免用户登录的安全隐患,由于篡改 Cookie 后无法生成对应的签名,服务器就可以知道用户对 Cookie 进行了篡改:

  1. 服务器配置一个 Secret 字符串,例如:X*suy112

  2. 当服务器需要设置 Cookie 时,会通过 Secret 字符串生成一个特殊签名,然后和true一起发送给客户端。

    例如使用哈希值Hash('true' + 'X*suy112')生成特殊签名

  3. 因为客户端篡改时没有 Secret 字符串,所以无法得到有效的签名,服务器借此可以判断 Cookie 是否别修改过。

但是这种防篡改机制也有漏洞:由于 Cookie 是明文传输的,所以只要服务器设置过一次签名,就可以知道 true 对应的签名,以后就可以使用这个签名来欺骗服务器。更好的做法是通过 Session ID 来防止这种问题。

Session

Session 是一种位于服务端的机制,可以存储在服务器的文件、数据库或内存中。利用 Session 将用户信息存储在服务端更加安全。

实现机制

当程序要为客户端的请求创建一个 Session 时,服务器首先检查客户端的请求中是否包含 Session ID:

  • 包含 Session ID:说明客户端之前已经创建过 Session,服务器只要把这个 Session 搜索(搜索不到就新建一个)出来使用就可以。
  • 不包含 Session ID:会为客户端创建一个 Session,并生成一个和 Session 相关的 Session ID 并发回给客户端。

    Session ID 的创建需要保证不会重复,并且规律复杂。通常保存 Session ID 的方式为 Cookie,字段为 SESSIONID。

实例

以存储在 redis 中的 Session 为例:

  1. 用户提交包含用户名和密码的表单,发送 HTTP 请求。

  2. 服务器验证完用户名和密码后,将用户对象存储到 redis 中并生成 Session ID。通过 Session ID 可以从 redis 中取出用户对象以及敏感数据。

  3. 设置 Cookie 并发送 HTTP 响应,这里仍然需要给每一项 Cookie 签名。

    例如设置sessionId = xxxxx|checksum

  4. 客户端收到响应报文后,之后的每次请求都将这个 Cookie 一起发送给服务器。服务器收到后根据 Session ID 进行防篡改验证。

  5. 通过防篡改验证后取出 redis 中的用户对象,查看用户对象的状态并继续执行业务逻辑。

需要注意如果 Cookie 被浏览器禁止时,通常会使用 URL 重写和表单隐藏字段这两种机制将 Session ID 发送给服务器:

  • URL 重写:将 Session ID 直接附加在 URL 路径后面;
  • 表单隐藏字段:服务器自动在表单里添加一个隐藏字段,表单提交时可以将 Session ID 一起发送给服务器。
  • Session 本质上是一种抽象的概念,开发人员将客户端和服务器之间的一对一交互抽象为 Session。
  • Cookie 本质上是一个实际存在的东西,是 HTTP 协议定义在 Header 中的实实在在的字段。

    两者本质上一个是概念,一个是实际存在。

后面进行比较时,Session 就代指一种为了避免 Cookie 的各种限制或者弊端,借助后端存储的 Session 实现方式。

存储方式

  • Cookie 只能保存 ASCII 字符串,保存 Unicode 字符或者二进制数据需要编码,同时也不能保存 Java 对象。
  • Session 由于存储在数据库或者内存中,可以存储任何类型的数据并可以保管 Java 对象。

    可以将 Session 看作一个 Java 容器类

隐私策略

Cookie 对客户端是可见的,所以可以被用户修改复制。Session 对于客户端是透明的,不存在信息安全问题。

有效期

  • Cookie 的过期时间属性允许它有很好的持久性。
  • Session 依赖于名为 JSESSIONID 的 Cookie,它的过期时间默许为 –1,只需关闭了阅读器 Session 就会失效。

    如果设置 Session 的超时时间过长,服务器累计的 Session 就会越多,容易导致内存溢出。

服务器压力

  • Cookie 保存在客户端,并发用户数量多的情况下不会占用服务器资源。
  • Session 保存在服务端,并发用户数量多会耗费大量的内存,产生一定的服务器压力。

浏览器支持

  • Cookie 需要浏览器的支持,浏览器不支持的会导致会话跟踪失效。WAP 上的应用无法使用常规 Cookie。
  • Session 只能在本浏览器窗口及其子窗口内有效。假如两个窗口互不相干,就会使用两个不同的 Session。

    WAP 应用可以使用 Session + URL 地址重写。IE8 下不同窗口 Session 相干

跨域支持

Cookie 支持跨域名访问。Session 不支持跨域名访问,仅在其所在的域名内有效。

例如将 domain 属性设置为“.biaodianfu.com”,则以“.biaodianfu.com”为后缀的一切域名均能够访问该 Cookie。可以通过自定义 Session ID 并设置 Cookie 的 domain 来实现 Session 的跨域传递。

参考

  1. Cookie 与 Session 的区别
  2. COOKIE 和 SESSION 有什么区别?-知乎
  3. Cookie/Session 的机制与安全
  4. sso 单点登录的入门(Session 跨域、Spring-Session 共享)