15.1 基本概念

若要区分来自不同用户的请求,服务端需要根据客户端提供的信息确认其用户身份,这个过程即为身份验证。

HTTP是一个“无状态”的协议,每个请求都完全独立,彼此之间没有任何关系、互不认识、没有关联。默认情况下服务端无法确认当前访问者的身份,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。然而,许多Web应用程序的安全和正常运行都要求系统能够区分并识别用户及其权限。这就需要一些附加的机制来维护HTTP的请求提供状态,给服务器与浏览器增加跟踪会话的能力(知道是谁在访问我)。

15.2 Cookie与Session验证

15.2.1 Cookie验证

1993 年,在浏览器发展初期,为了保持 Web 浏览状态,网景(Net Scape)公司雇员 Lou Montulli发明了今天广泛使用的 Cookie技术。Cookie是存储在客户端浏览器目录的一个简单文件(通常包含一个名称和一个值),它会在浏览器下次向同一服务器再发起请求时被自动携带并发送到服务器上。

有些 Cookie 是临时的,有些则是持续的。临时的 Cookie 只在浏览器上保存一段规定的时间,一旦超过规定的时间,该 Cookie 就会被系统清除。

Cookie 是不可跨域的 —— 每个 Cookie 都会绑定单一的域名,无法在别的域名下获取使用(一级域名和二级域名之间允许共享使用)。这样的规则可以确保仅源域能够访问其中存储的信息,第三方服务器既不能读取也不能更改用户计算机上该域的Cookie内容。

Cookie 必须在 HTML 文件内容输出之前设置。不同的浏览器对 Cookie 的处理不一致,但通常都允许客户端用户设置禁止使用 Cookie。 一个浏览器能创建的 Cookie 数量一般也有严格的限制:通常最多为 300 个,并且每个不能超过 4KB,每个 Web 站点能设置的 Cookie 总数一般不能超过 20 个。

基于 Cookie 的验证是有状态的,就是说验证或者会话信息必须同时在客户端和服务端保存。这个信息服务端一般在数据库中记录,而前端会保存在Cookie中。验证的一般流程如下:

image.png

1. 用户输入登陆凭据;
1. 服务器验证凭据是否正确,并创建会话,然后把会话数据存储在数据库中;
1. 具有会话ID的Cookie被放置在用户浏览器中;
1. 在后续请求中,服务器会根据数据库验证会话ID,如果验证通过则继续处理;
1. 一旦用户登出或超时,服务端和客户端同时销毁该会话。

目前很少有正式的Web应用系统单独使用 Cookie 做用户验证,因为简单应用 Cookie 会面临如下的问题:

  1. 一旦 Cookie 被劫持,全部信息都会泄露。并且客户端数据量变大,网络传输的数据量也会变大。
  2. Cookie 无法应对跨站请求伪造(CSRF)攻击。
  3. 大量的有的客户端不支持 Cookie,比如各种小程序。
  4. 浏览器对Cookie有不可改变的限制,比如小程序跳转H5页面不能携带 Cookie。
  5. 浏览器对单个 Cookie 保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个 Cookie。

    15.2.2 Session验证

    Session是另一种记录服务器和客户端会话状态的机制。他在服务器和客户端建立连接时添加会话标志(Session ID),不同的页面都能读取到这个这个全局的ID信息。Session信息存在于服务器端,较好地解决了安全问题。

实现Session的时候,通常会在服务器端软件转化为一个临时 Cookie 发送给给客户端,当客户端第一次请求时服务器会检查是否携带了这个 Session(临时 Cookie),如果没有则会添加 Session,如果有就拿出这个 Session来做相关操作。Session 是对于服务端来说的,客户端没有 Session 的说法。下图是 Session 验证的流程:

image.png
1. 用户第一次请求时,服务器根据用户提交的相关信息创建对应的 Session
1. 请求返回时将此 Session 的唯一标识 Session ID 返回给浏览器
1. 浏览器接收到返回的 Session ID,将其存到 Cookie,同时记录它属于哪个域名
1. 用户再次访问会自动把此域名的 Cookie 也发送给服务端,服务端从 Cookie 中取出 SessionID,再根据 SessionID 查找对应的 Session 信息。

根据以上流程可知,Session ID 是连接服务器与客户端的一道桥梁,大部分系统根据此原理来验证用户登录状态。

尽管 Session 比单纯的 Cookie 有很多的优点,但也面临难以解决的问题:

  1. 使用多个服务器做负载均衡或者使用分布式架构时,因为多个服务器不共享 Seesion 信息,没法有效判断当前用户是否经过验证。尽管可以将 Session 数据存在一个服务器中来解决,但是这就不能完全达到负载均衡的效果。
  2. 服务端需要存储所有用户的 Session 信息,这给服务器带来了额外的压力.

    15.2.3 Cookie 和 Session 的区别与联系

    虽然理论上只用 Cookie 就可以保持会话状态,但在正式的项目中并不会这样做,现在多数都是现在大多都是把 Session 和 Cookie 组合起来使用,即把 Seesion ID 存储在客户端的 Cookie 中(其它数据保存在服务端)。但这并不是实现 Session 的唯一的方法。如果客户端禁用 Cookie,还可以把 Session 信息放入URL中。

简而言之, 当组合使用的时候,Session 相当于用户信息档案表, 里面包含了用户的认证信息和登录状态等信息. 而 Cookie 就是用户通行证。

在使用这种组合方式的时候,客户端(前端)代码不需要做专门的开发,服务器端(后端)代码也只需要判断是否存 在Session、显式创建或销毁 Session,在 Session 中置入信息、在 Session 中寻找信息等操作。其他配合工作由客户端(浏览器)和服务器端(系统软件)自动完成。

15.3 Token认证

15.3.1 基本原理

Token 也称作令牌,通常被认为是访问资服务器源或应用程序接口(API)时所需要的凭持有的凭证。它认证方式类似于临时的证书签名,是一种服务端无状态的认证方式, 非常适合于 REST API 的场景。这里的无状态就是服务端并不会保存身份认证相关的数据。

常见的Token由三部分组成:

  • UID 用户唯一的身份标识
  • Time 当前时间的时间戳
  • Sign 签名,使用摘要或加密算法压缩成定长的十六进制字符串,以防止第三方恶意拼接

下图是Token的认证流程

image.png
1. 客户端使用某种手段请求登录,如用户名密码、手机短信验证等
1. 服务端验证成功后,签发 Token 自己保存并发送给客户端
1. 客户端存储收到的 Token
1. 客户端每次请求时将 Token 放入 Headers 中
1. 服务端校验 Token,成功则返回请求数据,失败则返回错误信息

在项目实践中,通常客户端(前端)代码会把 Token 显式存放于 Local Storage、Session Storage、或 Cookie中。服务器端则根据业务逻辑选择普通关系型数据库、分布式键值数据库(如 Redis)等系统保存 Token。当然,服务器端也可以选择完全不保存 Token 数据。

15.3.2 与Session的比较

Token 和 Session 相比有很大的优势:基于 Token 的验证是无状态的,这是它与 Cookie 相比的最大优点。服务器唯一的工作是在成功的登陆请求上签署 Token,然后在后续收到的访问中验证传入的 Token 是否有效。Token 完全由应用系统管理,所以它可以很好的应用于分布式系统和复杂均衡的服务器环境,能够避开同源策略在多个站点中使用,并且可以抵抗跨站请求伪造(CSRF)。Token同时支持PC浏览器和移动平台,并且效率更高。

当然 Session 和 Token 并不矛盾,Token 的安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重复攻击,而 Session 必须依赖链路层来保障通讯安全。如果你需要实现有状态的会话,仍然可以增加 Session 在服务器端保存一些状态。

15.4 结论

我们后续的开发使用 Token 作为用户验证手段。具体做法是在服务器(后端)应用JWT(JSON Web Token)完成 Token 的签发和解码,在前端代码中仅需把获得的 Token 保存在 Local Storage 或 Session Storage 中。在前端软件中配置全局的网络请求拦截器从 Storage 中读取 Token 并置入 Request Header。