session、cookie 和 token

142 阅读10分钟

概述

HTTP 协议是无状态的,所谓的无状态就是客户端每次想要与服务端通信,都必须重新与服务端链接,意味着请求一次客户端和服务端就连接一次,下一次请求与上一次请求是没有关系的。

这种无状态的方式就会存在一个问题:如何判断两次请求的是同一个人?就好比用户在页面 A 发起请求获取个人信息,然后在另一个页面同样发起请求获取个人信息,我们如何确定这俩个请求是同一个人发的呢?

为了解决这种问题,我们就迫切需要一种方式知道发起请求的客户端是谁?此时,cookie、token、session 就出现了,它们就可以解决客户端标识的问题,在扩大一点就是解决权限问题。

它们就好比让每个客户端或者说登录用户有了自己的身份证,我们可以通过这个身份证确定发请求的是谁!

cookie

cookie、session storage、local storage

概述

Cookie 是一种在客户端存储数据的技术,它是由服务器发送给客户端的小型文本文件,存储在客户端的浏览器中,大小限制大致在 4KB 左右。在客户端发送请求时,浏览器会自动将相应的 Cookie 信息发送给服务器。

弊端:

  • cookie 有存储大小限制,4KB 左右。
  • 浏览器每次请求会携带 cookie 在请求头中。
  • 字符编码为 Unicode,不支持直接存储中文。
  • 数据可以被轻易查看。

主要属性

其他的属性查看 MDN 文档

  • isHttpOnly

    指定该 Cookie 无法通过 JavaScript 脚本拿到,比如 Document.cookie 属性、 XMLHttpRequest 对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读 到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie。

csrf、xss 前端安全

使用流程

image.png

特点

  • cookie 存储在客户端
  • cookie 不可跨域,但是在如果设置了 domain,那么它们是可以在一级域名和二级域名之间共享的。

session

概述

session 由服务端创建。

当一个请求发送到服务端时,服务器会检索该请求里面有没有包含 sessionId 标识,如果包含了 sessionId,则代表服务端已经和客户端创建过 session,然后就通过这个 sessionId 去查找真正的 session,如果没找到,则为客户端创建一个新的 session,并生成一个新的 sessionId 与 session 对应,然后在响应的时候将 sessionId 给客户端,通常是存储在 cookie 中。

每一个客户端与服务端连接,服务端都会为该客户端创建一个 session,并将 session 的唯一标识 sessionId 通过设置 Set-Cookie 头的方式响应给客户端,客户端将 sessionId 存到 cookie 中。

使用流程

image.png

与 cookie 的关联及区别

cookie 和 session,它们两者之间主要是通过 sessionId 关联起来的,所以总结出:sessionId 是 cookie 和 session 之间的桥梁,session 是基于 cookie 实现的。

它们两个主要有以下特点:

  • session 比 cookie 更加安全,因为它是存在服务端的,cookie 是存在客户端的。
  • cookie 只支持存储字符串数据,session 可以存储任意数据。
  • cookie 的有效期可以设置较长时间,session 有效期都比较短。
  • session 存储空间很大,cookie 有限制。

系统想要实现鉴权,可以单独使用 cookie,也可以单独使用 session,但是建议结合两者使用。

token

概述

Token 是一种在客户端和服务端之间传递身份信息的方式。当用户登录成功后,服务端会生成一个 Token,将其发送给客户端。

客户端在后续的请求中,需要将 Token 携带在请求头或请求参数中。服务端通过验证 Token 的合法性,就可以确定该请求来自哪个用户,并且可以根据用户的权限进行相应的操作。

Token 可以有效地避免了 Cookie 的一些安全问题,比如 CSRF 攻击。

组成

Token是一个由一串字符组成的令牌,用于在计算机系统中进行身份验证和授权。它通常由三个部分组成:标头、有效载荷、签名。

  • 标头(Header):包含了算法和类型,用于指定如何对有效载荷进行编码和签名。常用的算法有HMAC、RSA、SHA等。
  • 有效载荷(Payload):包含了一些信息,如用户ID、角色、权限等,用于验证身份和授权。有效载荷可以是加密的,也可以是明文的。
  • 签名(Signature):是对标头和有效载荷进行签名后得到的值,用于验证token的完整性和真实性。签名通常使用私钥进行签名,并使用公钥进行验证。

一个完整的token包含了标头、有效载荷和签名三个部分,它们一起构成了一个安全的令牌,用于进行身份验证和授权。

使用流程

image.png

三者总结

cookie、session、token三者最终的目的都是一样:鉴权和认证。

实际使用

实现的这种登录状态的保存方案

  • 服务端存储的 session + cookie 的方案
  • 客户端存储的 jwt token 的方案

服务端存储的 session + cookie

概述

给 http 添加状态,那就给每个请求打上个标记,然后在服务端存储这个标记对应的数据。这样每个被标记的请求都可以找到对应的数据,自然可以做到登录、权限等状态的存储。

这个标记应该是自动带上的,所以 http 设计了 cookie 的机制,在里面存储的数据是每次请求都会带上的。

然后根据 cookie 里的标记去查找的服务端对应的数据叫做 session,这个标记就是 session 的 id。

image.png

弊端

CSRF

因为 cookie 会在请求时自动带上,那你在 A 网站登录了,再访问 B 网站,万一里面有个按钮会请求 A 网站的,那 cookie 依然能带上。而这时候就不用再登录了。

这样就很危险,而且一般这种利用 CSRF 漏洞的网站都会伪装的很好,让你很难看出破绽来,这种网站叫做钓鱼网站。

为了解决这个问题,我们一般会验证 referer,就是请求是哪个网站发起的,如果发起请求的网站不对,那就阻止掉。

但 referer 也是可以伪造的。

所以一般会用 随机值 来解决,每次随机生成一个值返回,后面再发起的请求需要包含这个值才行,否则就认为是非法的。

分布式 session

session 是把状态数据保存在服务端,那么问题来了,如果有多台服务器呢?

image.png

登录之后 session 是保存在某一台服务器的,之后可能会访问到别的服务器,这时候那台服务器是没有对应的 session 的,就没法完成对应的功能。

解决方案:

  • session 复制。也就是通过一种机制在各台机器自动复制 session,并且每次修改都同步下。这个有对应的框架来做,比如 java 的 spring-session。
  • 共享 session ,把 session 保存在 redis 。这样每台服务器都去那里查,只要一台服务器登录了,其他的服务器也就能查到 session,这样就不需要复制了。

分布式会话的场景,redis + session 的方案更常用一点。

跨域

cookie 为了安全,是做了 domain 的限制的,设置 cookie 的时候会指定一个 domain,只有这个 domain 的请求才会带上这个 cookie。

那万一是不同 domain 的请求呢?也就是跨域的时候,怎么带 cookie 呢?

a.xxx.com 和 b.xxx.com 这种还好,只要把 domain 设置为顶级域名 xxx.com 就可以了,那二三级域名不同也能自动带上。

但如果顶级域名也不同就没办法了,这种只能在服务端做下中转,把这俩个域名统一成同一个。

ajax 请求跨域的时候是不会挟带 cookie 的,除非手动设置 withCredentials 为 true 才可以。

而且也要求后端代码设置了对应的 header:

Access-Control-Allow-Origin: "当前域名"; 
Access-Control-Allow-Credentials: true

这里的 allow origin 设置 * 都不行,必须指定具体的域名才能接收跨域 cookie。

谷歌禁用第三方 cookie

session + cookie 总结

session + cookie 的给 http 添加状态的方案是服务端保存 session 数据,然后把 id 放入 cookie 返回。

cookie 是自动携带的,每个请求可以通过 cookie 里的 id 查找到对应的 session,从而实现请求的标识。

这种方案能实现需求,但是有 CSRF、分布式 session、跨域等问题,不过都是有解决方案的。

客户端存储的 token

概述

token 的方案常用 json 格式来保存,叫做 json web token,简称 JWT。

JWT 是由 header、payload、verify signature 三部分组成的。

弊端

安全性

JWT 把数据直接 Base64 之后就放在了 header 里,那别人就可以轻易从中拿到状态数据,比如用户名等敏感信息,也能根据这个 JWT 去伪造请求。

JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

JWT 要搭配 https 来用,让别人拿不到 header。

HTTPS 提供了对服务器身份的验证,可以确保客户端与正确的服务器进行通信,防止中间人攻击。这对于 JWT 尤为重要,因为 JWT 通常用于验证用户身份或授权访问资源,确保只有合法的用户或客户端可以使用 JWT 来访问受保护的资源。

如何保证安全?

  • jwt 的 payload 中要设置 expire 时间
  • 发送 jwt 要使用 https;不使用 https 发送的时候,jwt 里不要写入秘密数据

性能

JWT 把状态数据都保存在了 header 里,每次请求都会带上,比起只保存个 id 的 cookie 来说,请求的内容变多了,性能也会差一些。

使用过程中无法废弃某个Token

JWT 保存在客户端,服务端没法手动让它失效的。比如踢人、退出登录、改完密码下线这种功能就没法实现。

这是 JWT 最大的缺点,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

但可以配合 redis 来解决,记录下每个 token 对应的生效状态,每次先去 redis 查下 jwt 是否是可用的,这样就可以让 jwt 失效。

token 总结

JWT 的方案是把状态数据保存在 header 里,每次请求需要手动携带,没有 session + cookie 方案的 CSRF、分布式、跨域的问题,但是也有安全性、性能等问题。

Acesss Token 与 Refresh Token

实际使用总结

http 是无状态的,也就是请求和请求之间没有关联,但我们很多功能的实现是需要保存状态的。

给 http 添加状态有两种方式:

session + cookie:把状态数据保存到服务端,session id 放到 cookie 里返回,这样每次请求会带上 cookie ,通过 id 来查找到对应的 session。这种方案有 CSRF、分布式 session、跨域的问题。

jwt:把状态保存在 json 格式的 token 里,放到 header 中,需要手动带上,没有 cookie + session 的那些问题,但是也有安全性、性能的问题。

参考

Nest 通关秘籍

一文彻底搞清session、cookie、token的区别 - 知乎 (zhihu.com)

详解 Cookie,Session,Token - 掘金 (juejin.cn)