目录
0、HTTP是无状态的
2、服务端存储session库
2.1、我们先来简单聊聊session是什么?
2.2、session的存储方式
2.3、session的过期和销毁
2.4、session的分布式问题
2.5、session的缺点
3、token
3.1、理解token
3.2、客户端存储token
3.3、token过期
3.4、token的编码
4、JWT
4.1、简单介绍JWT
4.2、JWT的组成
4.2.1、JWT头
4.2.2、有效载荷【用户信息】
4.2.3、签名哈希【防伪标志】
Base64URL算法:
4.3、JWT的用法
4.4.JWT优缺点
4.5、refresh token
4.6、session token
5、单点登录
5.1、什么是单点登录?
5.2、“虚假”的单点登录(主域名相同)
5.3、“真实”的单点登录(主域名不同)
标注:
http协议是无状态的,也就是说http请求方和响应方无法维护状态,都是一次性的。但是在有的系统中,例如前面做过的OJ项目,都是刷题时,是需要用户登录后才能刷题。在这种情况下,我们是需要维护用户状态,否则用户就得不停地去登录登录,很影响用户体验感~
那么,我们如何进行去维护用户的状态呢?
标记!!!
也就是说,我们可以给已经登录的用户进行一个标记。当后端登陆成功后,就给这个用户做一个标记,携带着这个标记返回到前端;前端获取到这个标记后,把这个标记存储起来;当前端后续再有请求发送给后端时,我们把这个标记带着;后端再次收到请求后,先验证这个标记是否合法,合法则进行后续逻辑操作,不合法就表示这个用户还未登陆。
总结来说,就是:后端发标记——前端存标记——请求带标记
具体落实下来,有以下几个方案:cookie+session、token
前端存储,可以使用cookie、localStorage、sessionStorage等方式。如果采用cookie存储,cookie相比其他方式,借助http头、浏览器能力,cookie可以做到前端无感知。
大致过程:
通过配置Domain / Path 来限制cookie的空间范围:Domain限制域,Path限制路径。
Domain说明:Domain属性指定浏览器在发出HTTP请求时,哪些域名要附带这个cookie,如果没有指定这个属性,浏览器会默认是将其设为当前URL(存cookie的这个URL)的一级域名,例如www.baidu.com,就会设为baidu.com,并且如果访问baidu.com的任何子域名,HTTP请求也会带上这个cookie。并且如果服务器在Set-Cookie字段指定的域名不属于当前域名,浏览器会拒绝这个Cookie。也就是说,你自己本来的域名是aaa,你在Set-Cookie字段指定的域名是bbb,浏览器就会拒绝这个cookie。
Path说明: Path属性指定浏览器发出HTTP请求时,
通过配置Expires / Max-Age来限制cookie的时间范围~
Expires说明:Expires属性指的是具体的到期时间,到了指定时间后,浏览器就不再保留这个cookie。这个值的格式为UTC格式~
注:浏览器的时间是根据本地时间计算的,由于本地时间是不精确的,所以没有办法保证cookie一定会在服务器指定的时间过期~
Max-Age说明:Max-Age属性,指的是这个cookie存在开始算起的秒数,秒数到了后,浏览器就不再存储这个cookie了
注:上述两个属性同时设置了,Max-Age优先生效。
注:如果不设置上述两个属性,或者将其设置为null,则表示cookie只在当前回话有效,浏览器关闭后,当前session结束,该cookie就会被删除~
通过Secure / HttpOnly来限制cookie的使用方式~
Secure说明:Secure属性指定浏览器只有在加密协议hTTPS下,才能将这个cookie发送给服务器。这个属性不需要我们自己来配置,他只是一个开关,如果当前协议是HTTP,浏览器就会默认关闭这个属性,如果是HTTPS就会自动打开这个开关~
HttpOnly说明:HttpOnly属性也可以看作是一个开关,当我们把开关打开时,则指定该cookie无法通过js脚本(js中的document.cookie等操作)拿到这个值,防止cookie被脚本读到,也就是只有浏览器发出HTTP请求时,才会带上该cookie。
上述的简单了解后,我们知道他其实已经自动将session的SessionId放到了cookie中存储,也就是说前端的cookie中,存储了SessionId。在实际项目中,我们在使用session时,一般会在服务器端存储session会话,session回话中,包含sessionID,用户信息等,在我们有新的请求发送时,我们就可以直接获取cookie中存储的SessionID,然后在服务器端存储的session回话中查找,看有没有sessionID和请求中带过来的ID一样的会话,例如登录相关,有这个会话则表示已登录,无则表示用户未登录~
session的登录验证流程:
说明:
前端只是存了一个sessionId,session的具体信息,还是需要我们自己存储一下滴~
存储方式:
删除存储的session中的数据即可~
通常服务端是集群,而用户请求过来会走一次负载均衡,不一定打到哪台机器上。那一旦用户后续接口请求到的机器和他登录请求的机器不一致,或者登录请求的机器宕机了,session 不就失效了吗?
这个问题现在有几种解决方式。
但通常还是采用第一种方式,因为第二种相当于阉割了负载均衡,且仍没有解决「用户请求的机器宕机」的问题。
session的维护给服务器端会造成很大的困扰,我们必须找地方存放它,又要考虑分布式问题,甚至还要给他启用一套redis集群~
这个缺点,token就有效解决了~
例如在登录中,我们其实不需要往session中存太多东西,我们完全可以把要存的数据(轻量级数据)都打包到cookie中,这样一来,服务器就不同存了~ 而每次的请求来了,我们只需要验证cookie中的“证件”是否有效就可以了。
上述这种方式就被叫做token。
token的大致工作流程:
说明:
使用cookie或localStorage或sessionStorage等...
把过期时间和数据一起放过去,验证时判断就可以了
token的编码是base64编码,我们可以搜索base64,随便打开一个编码网站,如下:
上述这种把某些信息放在一起,通过base64编码后,好像没什么用。我大可以把cookie中的token值复制出来,解码,然后修改为我想要的用户的信息,再进行编码,放到cookie中,发送给后端,是不是就可以获取其他的用户信息了呢?
答案是肯定的。那我们应该怎么办呢?
针对上述这种操作,我们需要防止对token的数据这个数据的篡改!
如何防篡改?给token签名!!!
具体的我们可以根据JWT来看看,看看JWT是如何生成token的~
JWT是JSON WEB TOKEN的缩写,它是基于RFC7519标准定义的一种可以安全传输读的JSON对象,由于使用了数字签名,所以是可信任和安全全的。
之所以使用JWT,是因为Token是属于无状态的,每个人定制的规则不同,生成的的结果就会不同。所以目前,多数都是采用JWT为统一令牌标准~
jwt实例可以在这个网站看看效果:JSON Web Tokens - jwt.io
例如,我们就按照网页上默认的点击生成看看:
我们可以看到,编码后的结果是有三个部分组成的,每个部分之间用 . 分割【上述中,各个部分对应的字体颜色是一致的】。三个部分的含义:
JWT头部分是一个描述JWT元数据的JSON对象,通常如下:
{
"alg": "HS256",
"typ": "JWT"
}
说明:
最后,使用Base64URL算法将上述JSON对象转换为字符串保存
这一部分是JWT主体内容部分,也是一个JSON对象,包含需要传递的数据。JWT指定七个默认的字段供选择:
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
除了上述默认的字段外,我们还可以自定义私有字段,如:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
注:默认情况下,JWT是未加密的,也就是说其他人可以解读你的内容,所以不要构建隐私信息字段。
最后,使用Base64URL算法将上述JSON对象转换为字符串保存
签名是对上面两部分数据签名。通过指定的算法生成哈希,以确保数据不会被篡改。
首先,需要指定一个密钥(secret)。该密钥存在服务器中,不向用户公开。然后使用JWT头中指定的签名算法(HS256)根据下列公式,生成签名:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims),
secret)
在计算出签名哈希后,JWT头、有效载荷、和签名哈希三个部分组成一个字符串,每个部分用“.”分隔,构成整个JWT对象
如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。 作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/“和”=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"=“去掉,”+“用”-“替换,”/“用”_"替换,这就是Base64URL算法。
客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。
此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。
业务接口用来鉴权的 token,我们称之为 access token。越是权限敏感的业务,我们越希望 access token 有效期足够短,以避免被盗用。但过短的有效期会造成 access token 经常过期,过期后怎么办呢?
一种办法是,让用户重新登录获取新 token,显然不够友好,要知道有的 access token 过期时间可能只有几分钟。
另外一种办法是,再来一个 token,一个专门生成 access token 的 token,我们称为 refresh token。
有了 refresh token 后,几种情况的请求流程变成这样:
如果 refresh token 也过期了,就只能重新登录了。
session和token的边界还是很模糊的,就像上面4.5提到的,refresh token也可能是以session的形式组织维护的。
狭义上,我们通常认为session是前端存在cookie上,数据存在服务器端;token是前端存在哪儿都行,数据存在token里。非要对比这两个,其实就是在对比前端存哪儿,数据存哪儿~
业务线越来越多,就会有更多业务系统分散到不同域名下,就需要「一次登录,全线通用」的能力,叫做「单点登录」。
简单的,如果业务系统都在同一主域名下,比如wenku.baidu.com
tieba.baidu.com
,就好办了。可以直接把 cookie domain 设置为主域名 baidu.com
,百度也就是这么干的。
比如滴滴公司,同时拥有didichuxing.com
xiaojukeji.com
didiglobal.com
等域名,种 cookie 是完全绕不开的。
这要能实现「一次登录,全线通用」,才是真正的单点登录。
这种场景下,我们需要独立的认证服务(就是把登录业务单拎出来,放在一台服务器上),通常被称为 SSO(Single Sign On)。
一次从A系统引发登录,到B系统不用登录的完整流程:
说明:
「完整版本:考虑浏览器的场景」
上面的过程看起来没问题,实际上很多 APP 等端上这样就够了。但在浏览器下不见得好用。
对浏览器来说,SSO 域下返回的数据要怎么存,才能在访问 A 的时候带上?浏览器对跨域有严格限制,cookie、localStorage 等方式都是有域限制的。
这就需要也只能由 A 提供 A 域下存储凭证的能力。一般我们是这么做的:
图中我们通过颜色把浏览器当前所处的域名标记出来。注意图中灰底文字说明部分的变化。
本文是根据下文魔改过来的,加了一些个人的观点
文章: 鉴权必须了解的5个兄弟:cookie、session、token、jwt、单点登录 - 知乎
好啦,本文结束咯,下期见