Cookie,Session和Token

7erry

Web的基石是HTTP协议。HTTP是经典的无状态协议,即每个请求都是全新、独立的。随着Web技术的发展,开发者迫切的需要一种技术方案,用以进行访问控制与鉴权,例如保存Client用户的登陆状态与识别。一个行之有效且开销足够小的解决方案是,添加一个首部行,用以区别和标记请求的发起者实现会话跟踪。这就是Cookie。

Client向Server发送请求后,Server为该请求生成并保存Cookie(一般是随机生成的字符串),然后在响应头中添加setCookie字段,字段值为返回给Client的Cookie。Client收到响应报文后,检查首部行,发现setCookie字段后,会将Cookie保存在本地,并在接下来的针对该服务器的请求报文中加上Cookie字段,服务器收到报文时检查Cookie,即可获得Client的状态信息

Cookie的主要属性

属性名描述
nameCookie 的名称
valueCookie 的值
commentCookie 的描述信息
domain可以访问该 Cookie 的域名
expiresCookie 的过期时间,具体某一时间
maxAgeCookie 的过期时间
pathCookie 的使用路径
secureCookie 是否使用安全协议传输,比如 SSL 等
versioncookie 使用的版本号
isHttpOnly指定该 Cookie 无法通过 JavaScript 脚本拿到,比如 Document.cookie 属性、XMLHttpRequest 对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie。

以加入购物车为例,每次浏览器请求后 Server 都会将本次商品 id 存储在 Cookie 中返回给Client,Client会将 Cookie 保存在本地,下一次再将上次保存在本地的 Cookie 传给 Server ,如此通过 Cookie 保存用户的商品 id,与购买状态

preview

但我们仔细一想不难发现,一旦进行的操作变多,Cookie带来的开销会非常大。一些已经被记录的信息会被反复的传输,这是完全不必要的。

Session

Session 简介

为了避免不必要的开销,Server会将绝大部分的用户状态信息保存在本地,而Cookie只保存用户的识别信息,让Server知道是谁发起了请求即可,这样每次请求只要在 Cookie 里带上用户的身份信息,请求体里也只要带上本次用户需要进行的操作信息,即可大大减少 Cookie 的体积大小,我们把这种能识别哪个请求由哪个用户发起的优化后的Cookie机制称为 Session(会话机制),生成的能识别用户身份信息的字符串称为 SessionId。Session是一个虚拟概念,Cookie是一个HTTP头部,通过Cookie传输SessionId是Session的一种实现。

Session 工作流程

Client第一次请求Server时,Server根据用户提交的相关信息,创建对应的 Session , 并为其分配唯一的 SessionId。Server返回响应报文时会将此 Session 的唯一标识信息 SessionID 通过Cookie返回给Client,即在响应头中设置 Set-Cookie。Set-Cookie 的字段值包含了 SessionId,它的格式如下:Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]。Client接收到Server返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名。当用户第二次访问Server的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给Server,Server会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。

Session 痛点

  • Seesion:每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加
  • CORS(跨域资源共享):当我们需要让数据跨多台移动设备上使用时,跨域资源的共享会是一个让人头疼的问题。在使用Ajax抓取另一个域的资源,就可以会出现禁止请求的情况
  • CSRF(跨站请求伪造):用户在访问银行网站时,他们很容易受到跨站请求伪造的攻击,并且能够被利用其访问其他的网站
  • 成本高:当反向代理进行负载均衡时,不同服务器间Session并不共用
    在这些问题中,成本问题是最突出的

一些现有的解决方案为

Session 复制

A 生成 Session 后复制到 B, C,这样每台机器都有一份 Session,用户请求实际到达哪一台Server,由于 Session 都能找到,故不会有问。
题这种方式虽然可行,但缺点也很明显:
同一样的一份 Session 保存了多份,数据冗余如果节点少还好,但如果节点多的话,特别是像阿里,微信这种由于 DAU 上亿,可能需要部署成千上万台机器,这样节点增多复制造成的性能消耗也会很大。
preview

Session 粘连

这种方式是让每个Client请求只打到固定的一台机器上,比如浏览器登录请求打到 A 机器后,后续所有的添加购物车请求也都打到 A 机器上,Nginx 的 sticky 模块可以支持这种方式,支持按 ip 或 Cookie 粘连等等,如按 ip 粘连方式如下

1
2
3
4
5
upstream tomcats {
ip_hash;
server 10.1.1.107:88;
server 10.1.1.132:80;
}

这样的话每个 client 请求到达 Nginx 后,只要它的 ip 不变,根据 ip hash 算出来的值会打到固定的机器上,也就不存在 Session 找不到的问题了,当然不难看出这种方式缺点也是很明显,对应的机器挂了怎么办?
preview

Session 共享

这种方式也是目前各大公司普遍采用的方案,将 Session 保存在 redis,memcached 等中间件中,请求到来时,各个机器去这些中间件取一下 Session 即可。缺点其实也不难发现,就是每个请求都要去 redis 取一下 Session,多了一次内部连接,消耗了一点性能,另外为了保证 redis 的高可用,必须做集群,当然了对于大公司来说, redis 集群基本都会部署,所以这方案可以说是大公司的首选了。
preview

Token (JWT)

JWT 简介

我们通常所说的Token都是JWT(JSON Web Token)
Session需要在本地保存,因为如果不保存这些Session id , 怎么验证Client发给我的Session id 的确是Server生成的?JWT采用了利用私钥生成签名的方案解决了这个问题

JWT组成

JWT 主要由三部分组成,每个部分用 . 进行分割,各个部分分别是

  • Header : 指定了签名算法
    Header 是 JWT 的标头,它通常由两部分组成:令牌的类型(即 JWT)和使用的 签名算法,例如 HMAC SHA256 或 RSA , 例如

      {
          "alg": "HS256",
          "typ": "JWT"
      }
    

    指定类型和签名算法后,Json 块被 Base64Url 编码形成 JWT 的第一部分

  • Payload : 指定用户 id,过期时间等非敏感数据
    Payload 中包含一个声明。声明是有关实体(通常是用户)和其他数据的声明。共有三种类型的声明:

    • registered
      包含一组建议使用的预定义声明,主要包括

      ISS

      签发人

      iss (issuer)

      签发人

      exp (expiration time)

      过期时间

      sub (subject)

      主题

      aud (audience)

      受众

      nbf (Not Before)

      生效时间

      iat (Issued At)

      签发时间

      jti (JWT ID)

      编号

    • public
      公共的声明,可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密
    • private
      自定义声明,旨在在同意使用它们的各方之间共享信息,既不是注册声明也不是公共声明
      例如
1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

然后 Payload Json 块会被Base64Url 编码形成 JWT 的第二部分。

  • Signature : 签名
    Server 根据 header 知道它该用哪种签名算法,再用密钥根据此签名算法对 head + Payload 生成签名
    签证信息由三部分组成
    • header (base64后的)
    • Payload (base64后的)
    • secret (密钥)

一个简易的生成签名的Python程序可能是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#! /bin/python
from hashlib import sha256
import hmac
import base64
header = '''
{
"alg": "HS256",
"typ": "JWT"
}
'''
payload = '''
{
"sub": "1234567890",
"name": "John Doe",
"admin": True
}
'''
header = header.encode('utf-8')
payload = payload.encode('utf-8')
sign = base64.b64encode(hmac.new(key, message, digestmod=sha256).digest())
sign = str(sign, 'utf-8')
print(sign)

因此一个简单的jwt将会是

1
2
Header        Payload       Signature
xxxxxxxxx.yyyyyyyyyyyyyyyy.zzzzzzzzzzzz

其中Header与Payload都会被Base64编码

JWT 工作流程

  • Client发起登录请求,比如用户输入用户名和密码后登录。
  • Server校验用户名和密码后,将用户 id 和一些其它信息进行加密,生成 token。Server将 token 响应给Client。
  • Client收到响应后将 token 存储下来。
  • 以后的发送请求后需要将 token 携带上,比如放在请求头中或者其它地方。
  • Server token 后校验,校验通过则正常返回数据
    当 server 收到浏览器传过来的 token 时,它会首先取出 token 中的 header + payload,根据密钥生成签名,然后再与 token 中的签名比对,如果成功则说明签名是合法的,即 token 是合法的。而且你会发现 payload 中存有我们的 userId,所以拿到 token 后直接在 payload 中就可获取 userid,避免了像 Session 那样要从 redis 去取的开销

这种方式很妙,只要 server 保证密钥不泄露,那么生成的 token 就是安全的,因为如果伪造 token 的话在签名验证环节是无法通过的,就此即可判定 token 非法

JWT 痛点

  • Token无法像Cookie一样设置生存时间
  • Token因为不受同源策略限制,因此可以被JS代码读取,Token也是不安全的

三者比较

方式特点优点缺点
Cookie1.存储在Client。2.请求自动携带 Cookie。3.存储大小 4KB。1.兼容性好,因为是比较老的技术。2.很容易实现,因为 Cookie 会自动携带和存储。1.需要单独解决跨域携带问题,比如多台服务器如何共享 Cookie。2.会遭受 CSRF 攻击。3.存储在Client,不够安全。
Session1.存储在Server。2.存储大小无限制。1.查询速度快,因为是个会话,相当于是在内存中操作。2.结合 Cookie 后很容易实现鉴权。3.安全,因为存储在Server。1.耗费服务器资源,因为每个Client都会创建 Session。2.占据存储空间,Session 相当于存储了一个完整的用户信息。
token1.体积很小。2.自由操作存储在哪里。1.安全,因为 token 一般只有用户 id,就算被截取了也没什么用。2.无需消耗服务器内存资源,它相当于只存了用户 id,Session 相当于存储了用户的所有信息。3.跨域处理较为方便,比如多台服务器之间可以共用一个 token。1.查询速度慢,因为 token 只存了用户 id,每次需要去查询数据库。

Session 是空间换时间,token 是时间换空间

Reference

https://zhuanlan.zhihu.com/p/625995458
https://cloud.tencent.com/developer/article/1704064
https://blog.csdn.net/Top_L398/article/details/109361680

  • Title: Cookie,Session和Token
  • Author: 7erry
  • Created at : 2023-09-10 00:00:00
  • Updated at : 2023-09-10 00:00:00
  • Link: http://7erry.com/2023/09/10/Cookie-Session和Token/
  • License: This work is licensed under CC BY-NC 4.0.