js|鉴权必须了解的5个知识点(cookie,session,token,jwt,单点登录)

从状态说起 [HTTP 无状态] 我们知道,HTTP是无状态的,也就是说,HTTP请求方和响应方间无法维护状态,都是一次性的,它不知道前后的请求都发生了什么
但有的场景下,我们需要维护状态,最常见的,一个用户登录微博,发布,关注,评论,都是应该在登录后的用户状态下的
[标记] 在学校或公司,入学入职那一天起,会录入你的身份、账户信息,然后给你发个卡,今后在园区内,你的门禁、打卡、消费都只需要刷这张卡
[前端存储] 这就涉及一发,一存,一带,发好办,登录接口直接返回给前端,存储就需要前端想办法了,前提是,你要把卡带在身上
前端的存储方式有很多

  • 挂载到全局变量上,但这是个[体验卡],一次刷新页面就没了
  • 可以存到cookie,localStorage里,这属于[会员卡],无论怎么刷新,只要浏览器没清掉或者过期,就一直拿着这个状态
基石:cookie
可是前端好麻烦啊,又要自己存,又要想办法带出去,有没有不用操心的?
有,cookie
cookie也是前端存储的一种,但相比于localStorage等其他方式,借助HTTP头,浏览器能力,cookie可以做到前端无感知
一般过程是这样的:
  • 在提供标记的接口,通过 HTTP 返回头的 Set-Cookie 字段,直接「种」到浏览器上
  • 浏览器发起请求时,会自动把 cookie 通过 HTTP 请求头的 Cookie 字段,带给接口
[配置:Domain / Path]
你不能拿清华的校园卡进北大
cookie 是要限制:通过Domain(域) / Path(路径)两级(空间范围)
Domain属性指定浏览器发出 HTTP 请求时,哪些域名要附带这个 Cookie。如果没有指定该属性,浏览器会默认将其设为当前 URL 的一级域名,比如 www.example.com 会设为 example.com,而且以后如果访问example.com的任何子域名,HTTP 请求也会带上这个 Cookie。如果服务器在Set-Cookie字段指定的域名,不属于当前域名,浏览器会拒绝这个 Cookie。
Path属性指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。只要浏览器发现,Path属性是 HTTP 请求路径的开头一部分,就会在头信息里面带上这个 Cookie。比如,PATH属性是/,那么请求/docs路径也会包含该 Cookie。当然,前提是域名必须一致
[配置:Expires / Max-Age]
你毕业了卡就不好使了
cookie 还可以限制 通过Expires,Max-Age中的一种 (时间范围)
Expires属性指定一个具体的到期时间,到了指定时间以后,浏览器就不再保留这个 Cookie。它的值是 UTC 格式。如果不设置该属性,或者设为null,Cookie 只在当前会话(session)有效,浏览器窗口一旦关闭,当前 Session 结束,该 Cookie 就会被删除。另外,浏览器根据本地时间,决定 Cookie 是否过期,由于本地时间是不精确的,所以没有办法保证 Cookie 一定会在服务器指定的时间过期。
Max-Age属性指定从现在开始 Cookie 存在的秒数,比如60 * 60 * 24 * 365(即一年)。过了这个时间以后,浏览器就不再保留这个 Cookie。
如果同时指定了Expires和Max-Age,那么Max-Age的值将优先生效。
如果Set-Cookie字段没有指定Expires或Max-Age属性,那么这个 Cookie 就是 Session Cookie,即它只在本次对话存在,一旦用户关闭浏览器,浏览器就不会再保留这个 Cookie
[配置:Secure / HttpOnly]
有的学校规定,不带卡套不让刷(什么奇葩学校,假设);有的学校不让自己给卡贴贴纸。
Secure属性指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。另一方面,如果当前协议是 HTTP,浏览器会自动忽略服务器发来的Secure属性。该属性只是一个开关,不需要指定值。如果通信是 HTTPS 协议,该开关自动打开
HttpOnly属性指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是Document.cookie属性、XMLHttpRequest对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie
[HTTP 头对cookie的读写] HTTP 返回的一个 Set-Cookie 头用于向浏览器写入「一条(且只能是一条)」cookie,格式为 cookie 键值 + 配置键值
Set-Cookie: username=jimu; domain=jimu.com; path=/blog; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

那我想一次多 set 几个 cookie 怎么办?多给几个 Set-Cookie 头(一次 HTTP 请求中允许重复)
[前端对cookie的读写] 前端可以自己创建 cookie,如果服务端创建的 cookie 没加HttpOnly,那恭喜你也可以修改他给的 cookie。
调用document.cookie可以创建、修改 cookie,和 HTTP 一样,一次document.cookie能且只能操作一个 cookie
document.cookie = 'username=jimu; domain=jimu.com; path=/blog; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly';

调用document.cookie也可以读到 cookie,也和 HTTP 一样,能读到所有的非HttpOnly cookie
console.log(document.cookie); // username=jimu; height=180; weight=80

那有了存储工具,接下来怎么做呢?
应用方案:服务端session 现在回想下,你刷卡的时候发生了什么?
其实你的卡上只存了一个 id(可能是你的学号),刷的时候物业系统去查你的信息、账户,再决定「这个门你能不能进」「这个鸡腿去哪个账户扣钱」
这种操作,在前后端鉴权系统中,叫 session
典型的 session 登陆/验证流程:
js|鉴权必须了解的5个知识点(cookie,session,token,jwt,单点登录)
文章图片

  1. 浏览器登录发送账号密码,服务端查用户库,校验用户
  2. 服务端把用户登录状态存为Session,生成一个sessionId
  3. 通过登录接口返回,把sessionId set到cookie上
  4. 此后浏览器再请求业务接口,sessionId 随cookie带上
  5. 服务端查sessionId校验session
  6. 成功后正常做业务处理,返回结果
[Session 的存储方式] 服务端只是给 cookie 一个 sessionId,而 session 的具体内容(可能包含用户信息、session 状态等),要自己存一下。存储的方式有几种:
Redis(推荐):内存型数据库,redis中文官方网站。以 key-value 的形式存,正合 sessionId-sessionData 的场景;且访问快。
内存:直接放到变量里。一旦服务重启就没了
数据库:普通数据库。性能不高
[Session 的过期和销毁] 很简单,只要把存储的 session 数据销毁就可以
应用方案:token
我又想到学校,在没有校园卡技术以前,我们都靠「学生证」。门卫小哥直接对照我和学生证上的脸,确认学生证有效期、年级等信息,就可以放行了
回过头来想想,一个登录场景,也不必往 session 存太多东西,那为什么不直接打包到 cookie 中呢?这样服务端不用存了,每次只要核验 cookie 带的「证件」有效性就可以了,也可以携带一些轻量的信息
这种方式通常被叫做 token
js|鉴权必须了解的5个知识点(cookie,session,token,jwt,单点登录)
文章图片

token的流程是这样的:
  1. 用户登录,服务端校验账号密码,获取用户信息
  2. 把用户信息,token配置编码成token,通过cookie set到浏览器
  3. 此后用户请求业务接口,通过cookie携带token
  4. 接口校验token有效性,进行正常业务接口处理
session 和 token 狭义上,我们通常认为 session 是「种在 cookie 上、数据存在服务端」的认证方案,token 是「客户端存哪都行、数据存在 token 里」的认证方案。对 session 和 token 的对比本质上是「客户端存 cookie / 存别地儿」、「服务端存数据 / 不存数据」的对比。
单点登录 前面我们已经知道了,在同域下的客户端/服务端认证系统中,通过客户端携带凭证,维持一段时间内的登录状态。
但当我们业务线越来越多,就会有更多业务系统分散到不同域名下,就需要「一次登录,全线通用」的能力,叫做「单点登录」
虚假”的单点登录(主域名相同) 简单的,如果业务系统都在同一主域名下,比如wenku.baidu.com tieba.baidu.com,就好办了。可以直接把 cookie domain 设置为主域名 baidu.com,百度也就是这么干的
“真实”的单点登录(主域名不同) 比如滴滴这么潮的公司,同时拥有didichuxing.com xiaojukeji.com didiglobal.com等域名,种 cookie 是完全绕不开的。
这要能实现「一次登录,全线通用」,才是真正的单点登录。
【js|鉴权必须了解的5个知识点(cookie,session,token,jwt,单点登录)】这种场景下,我们需要独立的认证服务,通常被称为 SSO
js|鉴权必须了解的5个知识点(cookie,session,token,jwt,单点登录)
文章图片

  1. 用户进入A系统,没有登录凭证(ticket),A系统给他跳到SSO
  2. SSO没登录过,也就没有sso系统下没有凭证(注意这个和前面 A ticket是两回事),输入账号密码登录
  3. SSO账号密码验证成功,通过接口返回做两件事,一是种下sso系统下凭证(记录用户登录状态);二是下发一个ticket
  4. 客户端拿到ticket,保存起来,带着请求系统A接口
  5. 系统A检验ticket,成功后正常处理业务请求
  6. 此时用户第一次进入系统B,没有登录凭证(ticket),B系统给他跳到SSO
  7. SSO登录过,系统下有凭证,不用再次登录,只需要下发ticket
  8. 客户端拿到ticket,保存起来,带着请求系统B接口
总结
  • HTTP是无状态的,为了维持前后请求,需要前端存储标记
  • cookie 是一种完善的标记方式,通过 HTTP 头或 js 操作,有对应的安全策略,是大多数状态管理方案的基石
  • session 是一种状态管理方案,前端通过 cookie 存储 id,后端存储数据,但后端要处理分布式问题
  • token 是另一种状态管理方案,相比于 session 不需要后端存储,数据全部存在前端
  • token 的编码技术,通常基于 base64,或增加加密算法防篡改,jwt 是一种成熟的编码方案
  • session 和 token 的对比就是「用不用cookie」和「后端存不存」的对比
  • 单点登录要求不同域下的系统「一次登录,全线通用」,通常由独立的 SSO 系统记录登录状态、下发 ticket,各业务系统配合存储和认证 ticket

    推荐阅读