我们访问一个购物网站的服务器,会话就被创建了,然后就点击浏览商品,对感兴趣的商品就先加入购物车,等待一起付账,这看起来是很普通的操作,但是想一下,如果有很多别的电脑上的浏览器同时也在访问该购物网站的服务器,跟我们做类似的操作呢?服务器又是怎么记住用户,怎么知道用户 A 购买的任何商品都应该放在 A 的购物车内,不论是用户 A 什么时间购买的,不能放入用户 B 或用户 C 的购物车内的呢?
1. HTTP 协议的无状态性
以前,WEB 网页基本上是浏览的功能,但随着交互的兴起,有了登录、加入购物车、下单的功能。
由于 HTTP 是无状态的,每次请求是独立的,那么服务器端无法判断是谁发起的请求
那么如何突破 HTTP 无状态的限制
客户端-服务器,通过会话标识,来记录客户端的身份。
- 当客户客户端用账户密码发起请求成功
- 服务器主动给客户端返回一个登陆成功的会话标识
- 客户端以后请求服务器的时候携带这个会话标识
- 服务器监测到这个会话标识与数据库中一致,就认为已经登录成功
2. 解决方案
所以为了解决会话机制的问题,有了以下的几种解决方案
- Session-Cookie 机制
- Token 验证
- JWT
3. Session-Cookie 认证机制
Cookie 通过客户端记录用户信息,Session 通过服务端记录用户信息 !
简单来说就是 Cookie 是存储在客户端的(浏览器),而 Session 是存储在服务端的。
3.1. Cookie、Session
Cookie
Cookie 是由客户端保存的小型文本文件(最大支持 4096 字节),其内容为一系列的键值对。 是由 W3C 组织提出的,目前已经成为标准,所有主流浏览器都支持 Cookie。 Cookie 的作用是与服务器进行交互,作为 http 规范的一部分而存在的。
对于跨站请求,浏览器是不会发送凭证信息的,但是如果将请求头中的 withCredentials = true,才会带上该 ajax 请求所在域的 Cookie,从而使 Cookie 可以随着请求发送。
Session
Session 是由应用服务器维持的一个服务器端的存储空间,用户在连接服务器时,会由服务器生成一个唯一的 SessionID,用该 SessionID 为标识符来存取服务器端的 Session 存储空间。
而 SessionID 这一数据则是保存到客户端,用 Cookie 保存的,用户提交页面时,会将这一 SessionID 提交到服务器端,来存取 Session 数据。这一过程,是不用开发人员干预的。所以一旦客户端禁用 Cookie,那么 Session 也会失效。
3.2. Session-Cookie 使用
- 当一个 Session 开始时,Servlet 容器会创建一个 HttpSession 对象,那么在 HttpSession 对象中,可以存放用户状态的信息。
- Servlet 容器为 HttpSession 对象分配一个唯一标识符即 Sessionid,Servlet 容器把 Sessionid 作为一种 Cookie 保存在客户端的 浏览器 中。
- 用户每次发出 Http 请求时,Servlet 容器会从 HttpServletRequest 对象中取出 Sessionid,然后根据这个 Sessionid 找到相应的 HttpSession 对象,从而获取用户的状态信息。
Session 结束生命周期的两种办法:
- 一个是 Session.invalidate()方法,不过这个方法在实际的开发中,并不推荐,可能在强制注销用户的时候会使用。
- 一个是当前用户和服务器的交互时间超过默认时间后,Session 会失效。
3.3. 存在问题
- 当客户访问量增加,服务端需要存储大量的 Session 会话,对服务端有很大考验。
- 当服务端为集群时,用户登录其中一台服务器,会将 Session 保存在该服务器的内存中, 但是当用户访问其他服务器时,会无法访问。(已经有了成熟的解决方案)
- 可以采用使用缓存服务器来保证共享
- 第三方缓存来保存 Session
- 由于依赖 Cookie,所以存在 CSRF 安全问题。
基于这些问题一种解决方案是 Session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。就有了 Token 认证机制;另一种方案是服务器索性不保存 Session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
4. Token 认证机制
Acesss Token 全称,访问资源接口(API)时所需要的资源凭证。 简单 Token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,Token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)。
4.1. Token 使用
- 客户端使用用户名跟密码请求登录。
- 服务端收到请求,去验证用户名与密码。
- 验证成功后,服务端会签发一个 Token 并把这个 Token 发送给客户端。
- 客户端收到 Token 以后,会把它存储起来,比如放在 Cookie 里或者 localStorage 里。
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token。
- 服务端收到请求,然后去验证客户端请求里面带着的 Token ,如果验证成功,就向客户端返回请求的数据.
这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
4.2. Token 优点
- 可扩展性好
- 支持移动端设备
- 支持跨程序调用
Token 具体的实现可以是一个随机的字符串,也可以是标准的 JWT。
5. JWT 机制
JWT 是 Json WEB Token 缩写。它将用户信息加密到 Token 里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证 Token 的正确性,只要正确即通过验证。
5.1. JWT 原理
JWT 包含三个部分: Header 头部,Payload 负载和 Signature 签名。由三部分生成 Token,三部分之间用.
号做分割。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
5.2. JWT 使用
- 客户端使用用户名跟密码请求登录。
- 服务器返回的 JWT。
- 客户端收到服务器返回的 JWT,进行解析,可以储存在 Cookie 里面,也可以储存在 localStorage。
- 客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息 Authorization 字段里面。
Authorization: Bearer <Token>
- 服务器判断签名是否正确,判断 JWT Token 是否过期,如果验证成功,就向客户端返回请求的数据。
JWT 版是没有查库的,他所需要的基础信息可以直接放到 JWT 里,服务端只要判断签名是否正确就可以判断出该用户是否可以访问该接口,当然 JWT 里的内容也不是无限多的,其他更多的信息我们就可以通过 id 去查数据库。
5.3. JWT 的优点
- 轻量级:JWT 是非常轻量级的,传输的方式多样化,可以通过 URL/POST 参数/HTTP 头部等方式传输。
- 可重用性/扩展性:我们可以有许多独立的服务器在多个平台和域上运行,并重复使用相同的令牌来验证用户。
- 安全性:无需担心跨站请求伪造(CSRF)攻击。
5.4. JWT 的缺点
JWT 的最大缺点是,由于服务器不保存 Session 状态,因此无法在使用过程中废止某个 Token,或者更改 Token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效。
6. Session-Cookie、Token、JWT 区别
使用 Session-Cookie 时需要考虑的问题:
-
将 Session 存储在服务器里面,当用户同时在线量比较多时,这些 Session 会占据较多的内存,需要在服务端定期的去清理过期的 Session。
-
当网站采用集群部署的时候,会遇到多台 WEB 服务器之间如何做 Session 共享的问题。因为 Session 是由单个服务器创建的,但是处理用户请求的服务器不一定是那个创建 Session 的服务器,那么该服务器就无法拿到之前已经放入到 Session 中的登录凭证之类的信息了。
-
多个应用要共享 Session 时,除了以上问题,还会遇到跨域问题,因为不同的应用可能部署的主机不一样,需要在各个应用做好 Cookie 跨域的处理。
-
SessionId 是存储在 Cookie 中的,假如浏览器禁止 Cookie 或不支持 Cookie 怎么办? 一般会把 SessionId 跟在 url 参数后面即重写 url,所以 Session 不一定非得需要靠 Cookie 实现。
-
移动端对 Cookie 的支持不是很好,而 Session 需要基于 Cookie 实现,所以移动端常用的是 Token。
使用 Token 时需要考虑的问题
- 如果你认为用数据库来存储 Token 会导致查询时间太长,可以选择放在内存当中。比如 redis 很适合你对 Token 查询的需求。
使用 JWT 时需要考虑的问题
-
因为 JWT 并不依赖 Cookie 的,不需要担心跨域资源共享问题(CORS)。
-
JWT 默认是不加密,但也是可以加密的。JWT 不加密的情况下,不能将秘密数据写入 JWT。JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
-
JWT 一旦 JWT 签发了,到期之前就会始终有效,除非服务器部署额外的逻辑。
-
JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。JWT 适合一次性的命令认证,颁发一个有效期极短的 JWT,即使暴露了危险也很小,由于每次操作都会生成新的 JWT,因此也没必要保存 JWT,真正实现无状态。