session cookie token 的区别

HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性。

客户端请求服务端,服务端会为这次请求开辟一块内存空间,这个对象便是 Session 对象,存储结构为 ConcurrentHashMap。Session 弥补了 HTTP 无状态特性,服务器可以利用 Session 存储客户端在同一个会话期间的一些操作记录。

服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的 Set-Cookie:JSESSIONID=XXXXXXX 命令,向客户端发送要求设置 Cookie 的响应;客户端收到响应后,在本机客户端设置了一个 JSESSIONID=XXXXXXX 的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束。

https://cdn.xiaobinqt.cn/xiaobinqt.io/20220824/50c3ccd67e62465ab1beaa691995ed06.png
会话

接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie 信息(包含 sessionId ),然后,服务器通过读取请求头中的 Cookie 信息,获取名称为 JSESSIONID 的值,得到此次请求的 sessionId。

Session 机制有个缺点,比如 A 服务器存储了 Session,就是做了负载均衡后,假如一段时间内 A 的访问量激增,会转发到 B 进行访问,但是 B 服务器并没有存储 A 的 Session,会导致 Session 的失效。

HTTP 协议中的 Cookie 包括 Web Cookie浏览器 Cookie,它是服务器发送到 Web 浏览器的一小块数据。服务器发送到浏览器的 Cookie,浏览器会进行存储,并与下一个请求一起发送到服务器。通常,它用于判断两个请求是否来自于同一个浏览器,例如用户保持登录状态。

HTTP Cookie 机制是 HTTP 协议无状态的一种补充和改良

Cookie 主要用于下面三个目的

  • 会话管理

登陆、购物车、游戏得分或者服务器应该记住的其他内容

  • 个性化

用户偏好、主题或者其他设置

  • 追踪

记录和分析用户行为

Cookie 曾经用于一般的客户端存储。虽然这是合法的,因为它们是在客户端上存储数据的唯一方法,但如今建议使用现代存储 API。Cookie 随每个请求一起发送,因此它们可能会降低性能(尤其是对于移动数据连接而言)。

当接收到客户端发出的 HTTP 请求时,服务器可以发送带有响应的 Set-Cookie 标头,Cookie 通常由浏览器存储,然后将 Cookie 与 HTTP 标头一同向服务器发出请求。

参数名 作用
Max-Age 设置 cookie 的过期时间,单位为秒
Domain 指定了 Cookie 所属的域名
Path 指定了 Cookie 所属的路径
HttpOnly 告诉浏览器此 Cookie 只能靠浏览器 Http 协议传输,禁止其他方式访问
Secure 告诉浏览器此 Cookie 只能在 Https 安全协议中传输,如果是 Http 则禁止传输

Set-Cookie HTTP 响应标头将 cookie 从服务器发送到用户代理。下面是一个发送 Cookie 的例子

https://cdn.xiaobinqt.cn/xiaobinqt.io/20231009/68ec0f0d662d4a8ea816a2e4b1f301e3.png

此标头告诉客户端存储 Cookie

现在,随着对服务器的每个新请求,浏览器将使用 Cookie 头将所有以前存储的 Cookie 发送回服务器。

https://cdn.xiaobinqt.cn/xiaobinqt.io/20231009/88919b06618940d9b5c0bae71d0cfb86.png

有两种类型的 Cookies,一种是 Session Cookies会话 cookie,一种是 Persistent Cookies永久 cookie,如果 Cookie 不包含到期日期,则将其视为会话 Cookie。会话 Cookie 存储在内存中,永远不会写入磁盘,当浏览器关闭时,此后 Cookie 将永久丢失。如果 Cookie 包含有效期 ,则将其视为持久性 Cookie。在到期指定的日期,Cookie 将从磁盘中删除。

还有一种是 Cookie 的 SecureHttpOnly 标记。

上面的示例创建的是会话 Cookie ,会话 Cookie 有个特征,客户端关闭时 Cookie 会删除,因为它没有指定ExpiresMax-Age 指令。

但是,Web 浏览器可能会使用会话还原,这会使大多数会话 Cookie 保持永久状态,就像从未关闭过浏览器一样。

永久性 Cookie 不会在客户端关闭时过期,而是在特定日期(Expires)特定时间长度(Max-Age)外过期。例如

1
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;

安全的 Cookie 需要经过 HTTPS 协议通过加密的方式发送到服务器。即使是安全的,也不应该将敏感信息存储在cookie 中,因为它们本质上是不安全的,并且此标志不能提供真正的保护。

HttpOnly 的作用

  • 会话 Cookie 中缺少 HttpOnly 属性会导致攻击者可以通过程序( JS 脚本、Applet 等)获取到用户的 Cookie 信息,造成用户 Cookie 信息泄露,增加攻击者的跨站脚本攻击威胁。

  • HttpOnly 是微软对 Cookie 做的扩展,该值指定 Cookie 是否可通过客户端脚本访问。

  • 如果在 Cookie 中没有设置 HttpOnly 属性为 true,可能导致 Cookie 被窃取。窃取的 Cookie 可以包含标识站点用户的敏感信息,如 ASP.NET 会话 ID 或 Forms 身份验证票证,攻击者可以重播窃取的 Cookie,以便伪装成用户或获取敏感信息,进行跨站脚本攻击等。

DomainPath 标识定义了 Cookie 的作用域:即 Cookie 应该发送给哪些 URL。

Domain 标识指定了哪些主机可以接受 Cookie。如果不指定,默认为当前主机(不包含子域名)。如果指定了Domain,则一般包含子域名。

例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如developer.mozilla.org)。

例如,设置 Path=/docs,则以下地址都会匹配:

  • /docs
  • /docs/Web/
  • /docs/Web/HTTP

JSON Web Token,简称 JWT,它和 Session都可以为网站提供用户的身份认证,但是它们不是一回事。

在探讨 JWT 和 Session Cookies 之前,有必要需要先去理解一下它们的相同之处。

它们既可以对用户进行身份验证,也可以用来在用户单击进入不同页面时以及登陆网站或应用程序后进行身份验证。

如果没有这两者,那你可能需要在每个页面切换时都需要进行登录了。因为 HTTP 是一个无状态的协议。这也就意味着当你访问某个网页,然后单击同一站点上的另一个页面时,服务器的内存中将不会记住你之前的操作。

https://cdn.xiaobinqt.cn/xiaobinqt.io/20231009/dcc63c8881794018b714769f214547c3.png

因此,如果你登录并访问了你有权访问的另一个页面,由于 HTTP 不会记录你刚刚登录的信息,因此你将再次登录。

JWT 和 Session Cookies 就是用来处理在不同页面之间切换,保存用户登录信息的机制

也就是说,这两种技术都是用来保存你的登录状态,能够让你在浏览任意受密码保护的网站。通过在每次产生新的请求时对用户数据进行身份验证来解决此问题。

所以 JWT 和 Session Cookies 的相同之处是什么?那就是它们能够支持你在发送不同请求之间,记录并验证你的登录状态的一种机制。

Session Cookies 也称为会话 Cookies,在 Session Cookies 中,用户的登录状态会保存在服务器内存中。当用户登录时,Session 就被服务端安全的创建。

在每次请求时,服务器都会从会话 Cookie 中读取 SessionId,如果服务端的数据和读取的 SessionId 相同,那么服务器就会发送响应给浏览器,允许用户登录。

⚠️ 下图有点问题,域名应该一致。

https://cdn.xiaobinqt.cn/xiaobinqt.io/20231009/7de5d6b609354be5b1a8346b66e2643d.png

Json Web Token 的简称就是 JWT,通常可以称为 Json 令牌。它是RFC 7519 中定义的用于安全的将信息作为 Json 对象进行传输的一种形式。JWT 中存储的信息是经过数字签名的,因此可以被信任和理解。可以使用 HMAC 算法或使用 RSA/ECDSA 的公用/专用密钥对 JWT 进行签名。

使用 JWT 主要用来下面两点

  • 认证(Authorization):这是使用 JWT 最常见的一种情况,一旦用户登录,后面每个请求都会包含 JWT,从而允许用户访问该令牌所允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小。

  • 信息交换(Information Exchange):JWT 是能够安全传输信息的一种方式。通过使用公钥/私钥对 JWT 进行签名认证。此外,由于签名是使用 headpayload计算的,因此你还可以验证内容是否遭到篡改。

下面,我们会探讨一下 JWT 的组成和格式是什么

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

  • Header
  • Payload
  • Signature

因此,一个非常简单的 JWT 组成会是下面这样

https://cdn.xiaobinqt.cn/xiaobinqt.io/20231009/e32110689a834f6b86911766f9080ad3.png
JWT 组成

Header

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

例如

1
2
3
4
{
  "alg": "HS256",
  "typ": "JWT"
}

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

Payload

Token 的第二部分是 Payload,Payload 中包含一个声明。声明是有关实体(通常是用户)和其他数据的声明。共有三种类型的声明:registered, publicprivate 声明。

  • 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

JWT 的第三部分是一个签证信息,这个签证信息由三部分组成

  • header (base64后的)
  • payload (base64后的)
  • secret

比如我们需要 HMAC SHA256 算法进行签名

1
2
3
4
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

签名用于验证消息在此过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证 JWT 的发送者的真实身份

现在我们把上面的三个由点分隔的 Base64-URL 字符串部分组成在一起,这个字符串可以在 HTML 和 HTTP 环境中轻松传递这些字符串。

下面是一个完整的 JWT 示例,它对 header 和 payload 进行编码,然后使用 signature 进行签名

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

https://cdn.xiaobinqt.cn/xiaobinqt.io/20231009/00f5b92c8950490fa0d836e532dff8d0.png

如果想自己测试编写的话,可以访问 JWT 官网 https://jwt.io/#debugger-io

JWT 和 Session Cookies 都提供安全的用户身份验证,但是它们有以下几点不同

JWT 具有加密签名,而 Session Cookies 则没有。

JWT 是无状态的,因为声明被存储在客户端,而不是服务端内存中。

身份验证可以在本地进行,而不是在请求必须通过服务器数据库或类似位置中进行。 这意味着可以对用户进行多次身份验证,而无需与站点或应用程序的数据库进行通信,也无需在此过程中消耗大量资源。

Session Cookies 是存储在服务器内存中,这就意味着如果网站或者应用很大的情况下会耗费大量的资源。由于 JWT 是无状态的,在许多情况下,它们可以节省服务器资源。因此 JWT 要比 Session Cookies 具有更强的可扩展性

Session Cookies 只能用在单个节点的域或者它的子域中有效。如果它们尝试通过第三个节点访问,就会被禁止。如果你希望自己的网站和其他站点建立安全连接时,这是一个问题。

使用 JWT 可以解决这个问题,使用 JWT 能够通过多个节点进行用户认证,也就是我们常说的跨域认证

我们上面探讨了 JWT 和 Cookies 的不同点,相信你也会对选型有了更深的认识,大致来说

对于只需要登录用户并访问存储在站点数据库中的一些信息的中小型网站来说,Session Cookies 通常就能满足。

如果你有企业级站点,应用程序或附近的站点,并且需要处理大量的请求,尤其是第三方或很多第三方(包括位于不同域的API),则 JWT 显然更适合。

JWT(JSON Web Token)的解码包括以下步骤:

  1. 将 JWT 字符串分为头部、负载和签名三个部分。

  2. 使用 Base64URL 解码头部和负载,以获取原始的 JSON 数据。

  3. 使用头部中的算法信息(通常在 alg 字段中)来验证签名。在这种情况下,头部指定了使用 HS256 算法,这意味着需要使用相同的密钥对头部和负载部分进行签名,并验证签名是否匹配。

以下是使用 Golang 进行 JWT 解码的示例代码,不使用第三方包,但假设你已经有了密钥。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"
)

// 定义JWT结构体来解析头部和负载
type JWT struct {
	Header    map[string]interface{}
	Payload   map[string]interface{}
	Signature string
}

func main() {
	// 要解码的JWT字符串
	jwtString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTY5MDY5MTcsInVzZXJfaWQiOjF9.-CGzj1oGoQtk3PfY1ppkzGkaArGbvNX5eQIkKNGR4EE"

	// 使用点号分割JWT字符串,得到头部、负载和签名
	parts := strings.Split(jwtString, ".")
	if len(parts) != 3 {
		fmt.Println("Invalid JWT format")
		return
	}

	// 解码头部
	headerJSON, err := base64.RawURLEncoding.DecodeString(parts[0])
	if err != nil {
		fmt.Println("Error decoding header:", err)
		return
	}

	// 解码负载
	payloadJSON, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		fmt.Println("Error decoding payload:", err)
		return
	}

	// 解码头部和负载中的JSON数据
	var headerData, payloadData map[string]interface{}
	if err := json.Unmarshal(headerJSON, &headerData); err != nil {
		fmt.Println("Error unmarshaling header:", err)
		return
	}
	if err := json.Unmarshal(payloadJSON, &payloadData); err != nil {
		fmt.Println("Error unmarshaling payload:", err)
		return
	}

	// 解析签名
	signature := parts[2]

	// 创建JWT结构体
	jwt := JWT{
		Header:    headerData,
		Payload:   payloadData,
		Signature: signature,
	}

	// 打印解析结果
	fmt.Println("Header:", jwt.Header)
	fmt.Println("Payload:", jwt.Payload)
	fmt.Println("Signature:", jwt.Signature)
}

此示例假定你已经有用于验证签名的密钥,并且使用的是 HS256 算法。要验证签名是否有效,你需要使用相同的密钥对头部和负载部分进行签名,并比较得到的签名与 JWT 中的签名是否匹配。如果匹配,JWT 就是有效的。如果不匹配,说明 JWT 被篡改或无效。

  • 如果禁用了 Cookies,服务器仍会将 sessionId 以 cookie 的方式发送给浏览器,但是,浏览器不再保存这个 cookie (即 sessionId) 了。
  • 如果想要继续使用 session,需要采用 URL 重写的方式来实现,可以参考 https://www.cnblogs.com/Renyi-Fan/p/11012086.html

在线解密工具https://www.box3.cn/tools/jwt.html

npm 包https://www.npmjs.com/package/jwt-decode