浅谈 Cookie-Session 、Jwt 两种身份认证机制

HTTP 是无状态的协议

我们都知道 HTTP 是无状态(stateless)的协议:HTTP 对于事务处理没有记忆能力,不对请求和响应之间的通信状态进行保存。

使用 HTTP 协议,每当有新的请求发送时,就会有对应的新响应产生。协议本身并不保留之前一切的请求或响应报文的信息。这是为了更快地处理大量事务,确保协议的可伸缩性,而特意把 HTTP 协议设计成如此简单的。

可是,随着 Web 的发展,早期这种无状态的特性却带来了很多不方便性,比如说用户登录新浪微博,在登录页输入用户名、密码之后进入首页,但是由于 HTTP 是无状态的,HTTP 并不知道上一次的 HTTP 请求是否通过了验证,更无法得知当前用户的具体信息。

最简单的解决方案就是在所有的请求里面都带上用户名和密码,这样虽然可行,但是大大加重了服务器的负担(对于每个 request 都需要到数据库验证),而且用户也要每进入一个页面输入一次密码,毫无用户体验可言。

为此,引入了各种身份认证机制,这里说一下 Cookie-Session 和 Jwt 机制。

Cookie 是由 HTTP 服务器设置的,保存在浏览器中的小型文本文件,其内容为一系列的键值对。在 Chrome 中,通过开发者工具 -> Application -> Cookies 可查看
保存在浏览器中的 Cookie
这里简单介绍一下一些字段意思:
Expires:Cookie 的过期时间,默认过期时间为用户关闭浏览器时。
HttpOnly:指示浏览器不要在除了 HTTP(或者 HTTPS)请求之外暴露 Cookie。通过 JavaScript 脚本无法访问到 Cookie,能有效防止 XSS 攻击
Secure:设置 Cookie 的 Secure 属性为 true 时,意味着 Cookie 通信只限于加密传输,指示浏览器仅仅在通过安全/加密连接才能使用 Cookie。也就是说 Cookie 只有在 HTTPS 协议下才能上传到服务器,而 HTTP 协议下是无法上传的。

  1. 浏览器向某个 URL 发送请求
  2. 对应的服务器收到该 HTTP 请求,生成要发给浏览器的 HTTP 响应
  3. 在响应头中加入 Set-Cookie 字段,值为要设置的的Cookie
  4. 浏览器收到来自服务器的 HTTP 响应
  5. 浏览器在响应头中发现了 Set-Cookie 字段,就会将该字段的值保存在内存或者是硬盘中。
  6. 当下一次向该服务器发送 HTTP 请求时,会将服务器设置的 Cookie 附加在 HTTP 请求的字段 Cookie 中。
  7. 服务器收到这个 HTTP 请求之后,发现请求头中有 Cookie 字段,就知道了已经处理过这个用户的请求了。
  8. 过期的 Cookie 会被删除

Cookie 传递过程

什么是 Session?

相对于保存在浏览器中的 Cookie,Session 是存储在服务器端的,避免了在客户端中储存敏感数据。并且存取方式不同,Cookie 只能保存 ASCII 字符串,例如需要存取 Unicode 字符或者二进制数据,需要先进行编码。而Session中能够存取任何类型的数据。Session 一般配合 Cookie 使用,也就是接下来要说到的 Cookie-Session 机制。

  1. 用户输入登录信息
  2. 服务端验证登录信息是否正确,如果正确就在服务器端为这个用户创建一个 Session,并把 Session 存入数据库
  3. 服务器端会向客户端返回带有 sessionID 的 Cookie
  4. 客户端接收到服务器端发来的请求之后,看见响应头中的 Set-Cookie 字段,将 Cookie 保存起来
  5. 接下来的请求中都会带上这个 Cookie,服务器将 sessionID 和 数据库中的相匹配,如果有效则处理该请求
  6. 如果用户登出,Session 会在客户端和服务器端都被销毁
  1. 扩展性不好,当拥有多台服务器的情况下,如何共享 Session 会成为一个问题,也就是说,用户第一个访问的时候是服务器 A,而第二个请求被转发给了服务器 B,那服务器 B 无法得知其状态。(举例来说,A 网站和 B 网站是同一家公司的关联服务。用户只要在其中一个网站登录,再访问另一个网站自动登录)
  2. 安全性不好,攻击者可以利用本地 Cookie 进行欺骗和 CSRF 攻击。
  3. Session 保存在服务器端,如果短时间内有大量用户,会影响服务器性能。
  4. 跨域问题,Cookie 属于同源策略限制的内容之一。

Jwt 机制

JWT(JSON Web Token) 是由 RFC7519 定义的,是一个在双方之间安全的传达一组信息的 JSON 对象。

JWT 组成

JWT 由三个部分组成:header、payload、signature
每个部分中间使用 . 来分隔,其中,header 和 payload 使用 Base64URL 进行编码:

1
base64UrlEncode(header).base64UrlEncode(payload).signature

header 部分是一个 JSON 对象,用来描述 JWT 的元数据:

1
2
3
4
{
"typ": "JWT", // 表示对象是一个 JWT
"alg": "HS256" // 表示使用哪种 Hash 算法来创建签名,这里是 HMAC-SHA256
}

payload

payload 部分也是一个 JSON 对象,实际需要传递的数据被存放在这里。我们除了使用官方提供的七个字段之外,也可以使用自定义的私有字段。

1
2
3
4
{
"sub": "title",
"name": "Yeoman"
}

JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

signature

signature 是对前两个部分的签名,防止数据被篡改。

1
2
data = base64urlEncode( header ) + "." + base64urlEncode( payload );
signature = Hash( data, secret );

使用 Base64URL 编码的 header 和 payload 中间用 . 隔开,再使用 header 中指定的 Hash 算法,加上密钥对这个字符串进行 Hash 得到 signature

工作流程

  1. 前端将自己的用户名和密码发送到后端的接口
  2. 后端核对用户名和密码之后,将用户的一些信息作为 payload,生成 JWT
  3. 后端将 JWT 作为登录成功的返回结果返回给前端。前端可以将其结果保存在 localStorage/sessionStorage 中,登出时删除 JWT 即可。(最好不要保存在 Cookie 中,用了 Cookie 就不能设置 HTTPonly,并且存在跨域问题)
  4. 每一次请求都将 JWT 放在 HTTP 请求头中的 Authorization 位,这样相比放在 Cookie 中可以跨域。
1
Authorization: Bearer <token>
  1. 服务器解码 JWT,如果 token 有效,那么处理这个请求
  2. 用户登出,在客户端删除 token 即可,与服务端无关

JWT 特点

  1. JWT 默认是不加密的
  2. JWT 的目的是用来验证来源可靠性,并不是保护数据和防止未经授权的访问。(可以类比成一张电影票,只能验证电影票是否是真的,电影票也有一些基本信息,但是他人也可以使用你的电影票,如果可能的话)一旦暴露,任何人都可以获得权限。为了减少盗用,JWT 的有效期应该设置得比较短,对于一些比较重要的权限,使用时应该再次对用户进行认证。
  3. 最大的缺点是 token 过期处理问题,由于服务器不保存 Session 状态,因此无法在使用过程中废止或者更改权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

复习

这里再次复习一下相关知识:

同源策略限制的内容

  1. Cookie、LocalStorage、SessionStorage、IndexedDB 等存储性内容
  2. DOM 节点
  3. Ajax 发送请求后,结果被浏览器拦截
  1. 存取方式不同:Cookie 只能保存 ASCII 字符串,例如需要存取 Unicode 字符或者二进制数据,需要先进行编码。而Session中能够存取任何类型的数据
  2. 隐私策略不同:Cookie 存储在浏览器中,Session 存储在服务器上。
  3. 服务器压力不同:Session 是保管在服务器上的,每个用户都会产生一个 Session 。假如并发访问的用户十分多,会产生大量的 Session ,耗费大量的内存。

援引

JSON Web Token 入门教程
浅谈session,cookie和jwt(Json Web Token)认证方式
cookie-session机制与JWT机制对比
五个步骤轻松弄懂 JSON Web Token(JWT)

文章作者: Yeoman Li
文章链接: https://yeomanli.github.io/2019/08/16/浅谈 Cookie-Session 、Jwt 两种身份认证机制/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yeoman's Blog