目录
一. 问题引入
1.1 Web浏览器最初的作用
Web浏览器最初形成的时候,主要是提供用户查阅文档,浏览新闻。这些都是作为共享数据存储在服务器上的,每一个用户都可以通过发送HTTP请求获取服务器资源。
而且,每次的 HTTP 请求都是一个新请求,就是 "请求+响应" 。服务器会将每一个 HTTP 请求都当成一个新的 HTTP 请求,这也就是我们常说的 HTTP 请求是无状态的,哪怕我上一秒刚请求过,下一秒我再重新发送,它还是会把我当成新的请求来处理,不做记录不做区分,只负责处理。
1.2 交互网站的兴起与用户私有信息处理
后来,随着互联网技术的成熟,Web 浏览器不再只是提供查阅功能,开始出现各种各样的网上购物,需要用户登录才能操作的功能和网站。
此时,我们就迫切需要一种技术,将每个用户进行隔离,虽然HTTP请求是无状态的,但我们需要让每个用户发送过来的HTTP请求有状态。亦或者说让服务器可以区分哪些HTTP请求是由用户A发送的,哪些HTTP请求是由用户B发送的,哪些HTTP请求是由用户C发送的。
就这好比是打游戏,我用自己的账号登陆游戏服务器游玩,不可能说另一个人登陆游戏服务器玩的也是我的帐号吧;或者说在浏览器淘宝店铺,我买的商品,下的订单,不可能说另一个人进入之后也给他显示吧,这些都属于是每个用户的私有数据,不能像以前存储在服务器的共享文档数据一样公开。
为了能够实现用户隔离,由此,就有了 Session,Token 等一系列网络技术。
二. 最初的解决策略 Session
2.1 Session 基本原理
由于 HTTP 本身是么可有状态的,我们也不能改变不能 HTTP。
因此就有人提出可以使用标识,就是Session,可以翻译为"会话",服务器会分给客户端一个唯一的 Session ID,其实是一个唯一的随机的字符串,每个用户收到的 Session ID 都不相同,然后服务器记录 Session ID。下一次客户端再发送 HTTP 请求过来,就把标识 Session ID 也一并带过来,这样一来服务器就可以区分出哪些 HTTP 都是由哪些用户发送的了,然后就可以做出对应的数据处理。
2.2 Session 的缺点
上面说到了,当用户来进行访问时,生成一个 Session ID存储在服务器。那么随着用户量的逐渐增大,服务器内部就会存储大量的 Session ID,这无疑是巨大的资源开销,会导致服务器水平扩展能力太弱;此外,大量的Session ID存储在服务器上,如果哪一天服务器崩溃宕机了,那么所有携带 Session ID 再次来进行访的用户都会被判断为Session 已失效或过期,这一点对用户是不太友好的。
2.3 问题的解决方案
解决方案一:
到这里,许多小伙伴们会说可以多设置几台服务器,这样就能避免服务器崩溃带来的影响了,但与此同时也带来了另一个问题,多台服务器的 Session 不共享,如果服务器A生成了一个 Session,在将 Session 发送给用户的同时,也需要将这个 Session 发送给其他几台服务器,避免数据不同步导致用户下次访问时访问到其它服务器出现无此 Session 的情况。但是这样做也造成了很大的性能影响,因为以生成一个 Session,需要在多台服务器之间相互复制存储。
解决方案二:
后来人们又想出了另一种对应策略,将 Session 集中存储,我们姑且称之为 Session 池,然后让集群中的所有的服务器都去 Session 池中获取 Session 进行比对,这样一来就避免了 Session 在多台服务器上重复复制的问题了,并且服务器数量的增加也能提高并发时处理用户请求的效率。
但这样一来也会有另一个问题,如果存储 Session 池的服务器挂了怎么办?有同学会说那我给 Session 池也设置一个集群就好了,其实也可以,但这样一来需要服务器的规模就太大了,对于大的项目而言可以这样做,但对于小的项目来说,完全是资金的浪费。
无论哪种方法,我们可以看出一个共同的特点,都是 服务器存储机制。也就是说校验用户的信息资源存储在服务器端,并没有存储在客户端。
因此就有人提出,为什么不让客户端存储呢?由此就引出了我们要说的 "Token"。
三. Token 详解
Token 是有点类似于客户端存储机制。Session 则是服务端存储机制。
3.1 Token 的组成部分
Token 主要由 Header(头部)、Payload(载荷)、Signature(签名) 组成,它们之间使用 "." 分割,共同构成一个完整的 Token 字符串。如下图所示、
密钥本质上就是一个随机的 String 字符串,在登陆系统中提前定义好就可以了。
(1)Header 头部通常包含两部分,Token的类型和所使用的算法。(JWT就是下面我们要说的常用的Token类型之一,算法有HMAC SHA256、RSA等);
(2)Payload 载荷就是主体部分,主要存储以下三类用户数据;
- 用户信息:如用户名、用户 ID 等。
- 声明:如过期时间、权限等。
- 其他数据:如用户的偏好设置、访问历史等。
(3)Signature 签名是加密算法对 Header + Payload + 密钥 三者加密后生成的字符串。
3.2 Token 的原理
当用户进行登陆访问成功后,服务器基于上面说到的某种加密算法进行加密生成签名(也有人说是令牌,都可以),然后服务器将生成的签名(令牌)发送给用户,以后用户在进行访问就带着签名(令牌)过来(通常都存放在HTTP请求头)。
下一次用户再进行访问的时候,服务器先获取请求头中的 Token,然后使用和上述一样的方法再次重新生成签名,然后与用户访问携带的 Token 中的签名进行比对。
如果相等,则说明当前用户已经登陆过了,直接放行并根据 Token 中授予的权限进行授权。如果生成的 Token 与用户携带的 Token 不同,则说明用户的信息被篡改过,直接返回未授权禁止访问。
这种方法安全系数还是比较高的,很难破解,即使知道了加密的方式,但是不知道我系统的密钥,加密后的签名也无法与正确的签名匹配。
3.3 Token 和 JWT 的关系?
JWT 全称 "JSON Web Token",它是一种特定的 Token 实现方式,也是现在比较主流的一种 Token 实现方式,它生成的也是一个 Token,但是 JWT 使用 JSON 格式来存储数据,并且由于需要存储一些额外的信息,因此通常会比 Token 要更大一些。
现在的 Token 默认使用的就是HMAC SHA256 算法进行签名,生成 JWT。
除了 HMAC SHA256 算法之外,还有其他算法也可以生成 Token,例如使用 HMCA SHA384、HMCA SHA512、RSA、ECDSA 等多种加密算法;
这些算法的选择取决于具体的业务场景和需求,如果需要更高的安全性,可以选择 RSA 和 ECDSA,如果追求更快的签名速度,可以选择 SHCA SHA256 算法。
四. Cookie 详解
4.1 Cookie 是什么?有什么用?
一句话来说:"Cooke 是一种客户端存储技术"。
Cookie 可以用来存储小型数据文件,也可以存储 Session 和 Token,它是浏览器提供的一种机制。存于用户硬盘的一个文件,这个文件通常对应于一个域名,当浏览器再次访问这个域名时,便使这个cookie可用。因此,cookie可以跨越一个域名下的多个网页,但不能跨越多个域名使用。
举个最直观的例子,我在 谷歌浏览器上网浏览,谷歌浏览器会生成自己的 Cookie,我在 火狐浏览器上网,火狐浏览器也会生成自己的Cookie,而不能去访问和操作对方的 Cookie,因此 Cookie 不能跨越域名。
如下图,在浏览器中,按 F12 并打开 "Application",在左侧我们就可以看到 Cookies 这一栏,这里是全局 Cookie,所有的信息都存储在这里。点进去就可以看到里面存储着很多的数据,
另外,就是单个请求的 Cookie,如下图所示,我们找一个 HTTP 请求,打开 Headers,可以看到该请求的请求头 Request Header 也携带着 Cookie。
4.2 Cookie 的生成流程
通常情况下,Cookie 的生成分为以下四个步骤;
(1)用户首次登录访问网站发送请求到服务器——>(2)服务器发送一个 HTTP Resopnse 响应客户端,并将 Cookie 存放到头部——>(3)用户得到服务器响应,保存Cookie到浏览器——>(4)后续用户在发送请求到服务器,HTTP Request请求头都要携带Cookie然后服务器识别才会响应返回数据。
4.3 Cookie 常见属性
如下图所示,Cookie 中有很多属性,我们主要记住比较重要的几个就可以了。
name: | cookie的名称,一般用字母和数组表示; |
value: | 即 cookie 的值,可以进行加密和解密; |
path: | 访问路径,表示可以访问当 cookie 的路径都有哪些,"/" 表示同一个站点下均可以访问到; |
Expires/Max-Age: | 超时时间/最大存活时,就是字面意义上的意思,类似 Redis 中对象的TTL超时过期时间; |
HttpOnly: | 布尔属性值,用于指示 Cookie 是否只能通过 HTTP 请求发送,而不能通过 JavaScript 访问。如果设置了 HttpOnly 属性,那么 JavaScript 就无法访问这个 Cookie,从而增加了 Cookie 的安全性。 |
Secure: | 布尔属性,用于指示 Cookie 是否只能通过 HTTPS 请求发送。如果设置了 Secure 属性,那么 Cookie 只会在 HTTPS 请求中发送,而在 HTTP 请求中不会发送。这可以防止 Cookie 被窃取,因为 Cookie 只会在 HTTPS 请求中发送,而在 HTTP 请求中不会发送。 |
4.4 Cookie 的安全性问题
在 HTTP 中,由于Cookie 是进行明文传输的,所以安全性极低。特别是如果在 Cookie 中存储 Token,收到网络攻击后,对方可以直接获取 Cookie 中的Token登陆进行违法操作。而且,Cookie 的大小限制在 4KB 左右,对于业务比较发杂的信息是无法携带的,所以通常用来存储对安全性不高的简易数据。
既然 Cookie 无法存储 Token,那么实际开发过程中, Token 存在哪里呢?
其实是在 Redis 中,大致流程如下所示,代码省略,但思路大致就是这个样子。
// 1. 登录,验证成功,生成 token签名
// 2. 将 token 存储于 redis,key = user:token:uid
// 3. 将 key 返回给客户端,
// 4. 再次访问,客户端携带 key 访问接口,
// 5. 通过 key 查询 redis 获取 token
// 6. 拦截验证 token 签名,
// 7. 如果验证成功,则放行,否则拦截
4.5 Cookie 的生命周期
Cookie 现在有两种常见的存储级别,一种是会话级,一种是持久级。
会话级:Cookie 只保存在客户端浏览器的内存中,当我们关闭客服端时 Cookie 就失效了;
持久级:Cookie 会保存在用户的硬盘中,直至过期时间结束或者用户主动将其销毁;