鉴权授权(机顶盒鉴权)
众所周知,web server是无状态的,也就是说服务器不知道用户在最后一次请求中做了什么,请求之间是相互独立的。客户信息仅来自每个请求携带的或由服务器本身保存的公共信息,并且可以被所有请求使用。因此,为了跟踪用户请求的状态信息,比如网购的购物车历史,Cookie应运而生。
当服务器响应客户端的请求时,它会向客户端推送一个Cookie。这个Cookie记录了服务器上的一些信息。客户端在后续请求中携带这个Cookie,服务器可以根据这个Cookie判断请求的上下文。
Cookie的出现是从无国籍状态过渡到有国家状态的一种手段。
以登录为例,用户输入帐户名和密码,向服务器发送请求。服务器生成一个Cookie并发送给浏览器。浏览器将Cookie以k-v的形式保存在某个目录下的文本文件中,下次请求同一个网站时会将Cookie发送给服务器。服务器验证接收到的Cookie是否与服务器的Cookie一致;否则,验证失败。这是最初的想法。
img src=' https://P6 . /large/PGC-image/48e 7b 24 ea 95 f 4 E0 CB 8d 940 DC 05 feb 1 EC '/
存储在浏览器中的Cookie的位置如下图所示。
img src=' https://p26 . /large/PGC-image/b 35 b 701d 7 b 454 ada 95 a 49 a 36d 0026 a F9 '/
Cookie的原理决定了他具有以下特征:
在存储客户端,可以随意篡改,不安全。
它的内容会和http交互传输,会影响性能,所以Cookie能存储的数据不能太大,最多4kb。
一个浏览器只能为一个网站存储不超过20个cookie,而浏览器一般只允许300个cookie。
移动对Cookie支持不友好。
一般来说,存储的是纯文本,对象在存储之前需要序列化,解析需要反序列化。
还是以登录Cookie为例。例如,现在有两个二级域名,http://a.xxx.com(域名A)和3358b.xxx.com(域名B)。那么域名A的登录Cookie可以在域名B下使用吗?
默认情况下,域名A的服务主机中生成的Cookie只能由域名A的服务器获取,其他域名无法获取这个Cookie,只能是主机Cookie。
但是,服务器可以显式声明Cookie的域来定义它的域。比如域名A的登录Cookie的域设置为http://xxx.com(他们共同的顶级域名),path设置为'/',Set-Cookie:name=value;domain=xxx.comPath='/',那么域名B就可以读了。
在新规范rfc6265中,domain的值将忽略任何前导点,即子域中可以使用**xxx.com**和* * XXX.com * *。SSO(单点登录)也是根据这个原理实现的。
例如,现在有两个域名,a.b.e.f.com.cn(域名1)和c.d.e.f.com.cn(域名2)。域名2想读取域名1的Cookie。域名1可以声明哪些域?答案是e.ffcom.cn或者. f.com.cn,浏览器不能接受以domian为.com.cn的cookie,因为如果cookie域名可以设置为后缀的话,那就是峡谷大战了。
如果域名1设置了Set-Coo
kie:mykey=myvalue1;domain=e.f.com.cn;path=’/’域名 2 设置Set-Cookie:mykey=myvalue2;domain=e.f.com.cn;path=’/’
那该域下 mykey 的值会被覆盖为 myvalue2,很好理解,同一个域下,Cookie 的 mykey 是唯一的。通
常,我们要通过设置正确的 domain 和 path,减少不必要的数据传输,节省带宽。
随着交互式 Web 应用的兴起,Cookie 大小的限制以及浏览器对存储 Cookie 的数量限制,我们一定需要更强大的空间来储存大量的用户信息,比如我们这个网站是谁登录了,谁的购物车里加入了商品等等,服务器
要保存千万甚至更多的用户的信息,Cookie 显然是不行的。那怎么办呢?
那将用户信息存储在哪呢?能否直接存在服务器中?
如果存在服务器中,1、这对服务器说是一个巨大的开销,严重的限制了服务器的扩展能力。2、假设 web 服务器做了负载均衡,用户 user1 通过机器 A 登入该系统,那么下一个请求如果被转发到另一台机器 B 上,机器 B 上是没有存该用户信息的,所以也找不到 sessionId,因此 sessionId 不应该存储在服务器上。这个时候redis/Memcached便出来解决该问题了,可以简单的理解它们为一个缓存数据库。
当我们把 sessionId 集中存储到一个独立的缓存服务器上,所有的机器根据 sessionId 到这个缓存系统里去获取用户信息和认证。那么问题就迎刃而解了。
根据其工作原理,你有没有发现这个方式会存在一个什么样的问题?
那就是增加了单点登录失败的可能性,如果负责 session 的机器挂了, 那整个登录也就挂了。但是一般在项目里,负责 session 的机器也是有多台机器的集群进行负载均衡,增加可靠性。
思考:
假如服务器重启的话,用户信息会丢失吗?
Redis 等缓存服务器也是有个集群的,假设某一台服务重启了,会从其他运行的服务器中查找用户信息,那假设真的某一次所有服务器全都崩溃了,怎么办呢?大概的应对策略就是,存储在内存中的用户信息会定期刷到主机硬盘中以持久化数据,即便丢失,也只会丢失重启的那几分钟内的用户数据。
Cookie-session 局限性
依赖 Cookie,用户可以在浏览器端禁用 Cookie
不支持跨端兼容 app 等
业务系统不停的请求缓存服务器查找用户信息,使得内存开销增加,服务器压力过大
服务器是有状态的,如果是没有缓存服务器的方式,扩容就非常困难,需要在多台服务器中疯狂复制 sessionId
存在单点登录失败的可能性
单点登录(Single Sign On),简称为 SSO。随着企业的发展,一个大型系统里可能包含 n 多子系统,用户在操作不同的系统时,需要多次登录,很麻烦,单点登录就是用来解决这个问题的,在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。
之前我们说过,单点登录是基于 cookie 同顶域共享的,那按照不同的情况可分为以下 3 种类型。
同一个站点下;
系统在相同的顶级域名下;
各子系统属于不同的顶级域名
一般情况下一个企业有一个顶级域名,前面讲过了,同一个站点和相同顶级域下的单点登录是利用了 Cookie 顶域共享的特性,相信大家已经明白这个流程,不再赘述。但如果是不同域呢?不同域之间 Cookie 是不共享的,怎么办?
这里我们就要说一说 CAS(中央认证服务 )流程了,这个流程是单点登录的标准流程。它借助一个单独的系统专门做认证用,以下成为SSO系统。
它的流程其实跟 Cookie-session 模式是一样的,单点登录等于说是每个子系统都拥有一套完整的 Cookie-session 模式,再加上一套 Cookie-session 模式的 SSO 系统。
用户访问系统 a,需登录的时候跳到 SSO 系统,在 SSO 系统里通过账号密码认证之后,SSO 的服务器端保存 session,,并生成一个 sessionId 返回给 SSO 的浏览器端,浏览器端写入 SSO 域下的 Cookie,并生成一个生成一个 ST,携带该 ST 传入系统 a,系统 a 用这个 ST 请求 SSO 系统做校验,校验成功后,系统 a 的服务器端将登录状态写入 session 并种下系统 a 域下的 Cookie。之后系统 a 再做登录验证的时候,就是同域下的认证了。
这时,用户访问系统 b,当跳到 SSO 里准备登录的时候发现 SSO 已经登录了,那 SSO 生成一个 ST,携带该 ST 传入系统 b,系统 b 用这个 ST 请求 SSO 系统做校验,校验成功后,系统 b 的服务器端将登录状态写入 session 并设置系统 b 域下的 Cookie。可以看得出,在这个流程里系统 b 就不需要再走登录了。
关于“跳到 SSO 里准备登录的时候发现 SSO 已经登录了”,这个是怎么做的呢,这就涉及 Oauth2 授权机制了,在这里就不展开讲,简单提个思路,就是在系统 b 向 SSO 系统跳转的时候让它从系统 a 跳转,携带系统 a 的会话信息跳到 SSO,再通过重定向回系统 b。
关于 Oauth2,可移步阮一峰 的《OAuth 2.0 的四种方式》。
我们已经分析过 Cookie-session 的局限性了,还有没有更彻底的解决办法呢?既然 SSO 认证系统的存在会增加单点失败的可能性,那我们是不是索性不要它?这就是去中心化的思路,即省去用来存储和校验用户信息的缓存服务器,以另外的方式在各自系统中进行校验。实现方式简单来说,就是把 session 的信息全部加密到 Cookie 里,发送给浏览器端,用 cpu 的计算能力来换取空间。
服务端不保存 sessionId,用户登录系统后,服务器给他下发一个令牌(token),下一次用户再次通过 Http 请求访问服务器的时候, 把这个 token 通过 Http header 或者 url 带过来进行校验。为了防止别人伪造,我们可以把数据加上一个只有自己才知道的密钥,做一个签名,把数据和这个签名一起作为 token 发送过去。这样我们就不用保存 token 了,因为发送给用户的令牌里,已经包含了用户信息。当用户再次请求过来的时候我用同样的算法和密钥对这个 token 中的数据进行加密,如果加密后的结果和 token 中的签名一致,那我们就可以进行鉴权,并且也能从中取得用户信息。
对于服务端来说,这样只负责生成 token , 然后验证 token ,不再需要额外的缓存服务器存储大量的 session,当面对访问量增加的情况,我们只需要针对访问需求大的服务器进行扩容就好了,比扩充整个用户中心的服务器更节省。
假如有人篡改了用户信息,但是由于密钥是不知道的,所以 token 中的签名和被篡改后客户端计算出来的签名肯定是不一致的,也会认证失败,所以不必担心安全问题。
关于 token 的时效性,是这样做的,首次登陆根据账号密码生成一个 token,之后的每次请求,服务端更新时间戳发送一个新的 token,客户端替换掉原来的 token。
jwt 模式的退出登录实际上是假的登录失效,因为只是浏览器端清除 token 形成的假象,假如用之前的 token 只要没过期仍然能够登陆成功
安全性依赖密钥,一旦密钥暴露完蛋
加密生成的数据比较长,相对来说占用了更大的流量
不依赖 Cookie,可跨端跨程序应用,支持移动设备
相对于没有单点登录的 cookie-session 模式来说非常好扩展
服务器保持了无状态特性,不需要将用户信息存在服务器或 Session 中
对于单点登录需要不停的向 SSO 站点发送验证请求的模式节省了大量请求
相关问答:
下一篇:买房贷款这么算(房贷算法)