应用鉴权

应用鉴权就是当一个用户进入APP时,我们需要判断他所拥有的权利,根据权力来判断他所能进行的一个行为,最为常见的就是购物网站的登录以及购物支付等操作。


一.鉴权的需求背景

Http的请求是无状态的,就是说在一个Http请求中的请求方和响应方都是无法维护状态,是一次性的,所以我们就不知道请求前后都发生了什么。所以我们需要标记的功能,而浏览器的sessionStorage,localStorage,全局变量等限制太多,就有了cookie,session,token等鉴权的操作。

二.cookie

cookie也是一种前端存储的方式,但是他和sessionStorage,localStorage等本地存储的不同在于,浏览器向服务端发起请求的时候,cooike是自动传过去的,可以做到前端无感知,出错的概率更低
过程:
1.浏览器向服务器发起请求并传送数据,由服务器接收数据然后设置cooike放进响应头(Set-Cookie),浏览器接收到响应之后就会自动存储进cookie

2.在之后的每一次请求中浏览器都会自动的在请求头之中设置cookie字段,发送给服务端
配置:
1.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。当然,前提是域名必须一致。

2.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。

3.Secure / HttpOnly
cookie 可以限制使用方式。

Secure属性指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。另一方面,如果当前协议是 HTTP,浏览器会自动忽略服务器发来的Secure属性。该属性只是一个开关,不需要指定值。如果通信是 HTTPS 协议,该开关自动打开。
HttpOnly属性指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是Document.cookie属性、XMLHttpRequest对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie。

Http头对cookie的读写:
响应会携带一个Set-Cookie头,一个Set-Cookie只能设置一条cookie,格式为cookie键值+配置键值,如果想要一次设置多个cookie,我们可以多写几个Set-Cookie在头里面。

1
Set-Cookie: username=jimu; domain=jimu.com; path=/blog; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

而当浏览器请求服务器的时候,就不再需要发送配置内容了,只需要发送键值对就可以

1
Cookie: username=jimu; height=180; weight=80

前端对cookie的读写操作:
如果服务端设置的cookie并没有设置httpOnly,那么我们就可以调用document.cookie来对cookie进行读写操作,但一次调用document.cookie就只能操作一个cookie

三.session

在上面我们介绍了cookie,可以了解到cookie其实是浏览器存储的一种实现,可以看作应用鉴权的基石。但是它只是一个存储信息的工具,我们还需要判断其中的信息是不是安全的操作者,这时候我们就需要session了。
典型的session登录流程:

upload successful
1.浏览器登录发送账号密码,服务端查用户库,校验用户

2.服务端把用户登录状态存为 Session,生成一个 sessionId

3.通过登录接口返回,把 sessionId set 到 cookie 上,此后浏览器再请求业务接口,sessionId 随 cookie 带上

4.服务端查 sessionId 校验 session

5.成功后正常做业务处理,返回结果

session的存储与过期销毁:
由于session是用来验证的,所以服务端仅仅只是给浏览器的cookie中添加一个sessionid,所以也需要自己保存一下,存储的方式:

1.Redis(推荐):内存型数据库,redis中文官方网站。以 key-value 的形式存,正合 sessionId-sessionData 的场景;且访问快。

2.内存:直接放到变量里。一旦服务重启就没了

3.数据库:普通数据库。性能不高。

而session也可以手动设置过期时间,一到过期时间就直接清空存储的内存就好了

session的分布式问题:
由于服务端是集群,而用户请求过来会走一次负载均衡,不一定会打到哪台机器上。一旦用户后续接口请求到的机器和他登录请求的机器不一样,或者登录请求的机器出问题了,那session就会失效。
常见的解决方式:

1.把session集中存储到独立的redis或者普通数据库中,就可以把session存储到一个库里(存储角度

2.让相同 IP 的请求在负载均衡时都打到同一台机器上。以 nginx 为例,可以配置 ip_hash 来实现。(分布角度

四.token

upload successful

1.用户登录,服务端校验账号密码,获得用户信息

2.把用户信息、token 配置编码成 token,通过 cookie set 到浏览器

3.此后用户请求业务接口,通过 cookie 携带 token

4.接口校验 token 有效性,进行正常业务接口处理

目前主流的token存储还是在cookie中进行,但是为了防止伪造token造成的安全问题,我们还需要一些加密算法来生成签名,来保证数据安全性

upload successful
JWT
由于以上的方法增加了cookie的数量,所以JSON web Token得以被使用。

upload successful
refresh token
token,作为权限守护者,最重要的就是「安全」。

业务接口用来鉴权的 token,我们称之为 access token。越是权限敏感的业务,我们越希望 access token 有效期足够短,以避免被盗用。但过短的有效期会造成 access token 经常过期,过期后怎么办呢?

一种办法是,让用户重新登录获取新 token,显然不够友好,要知道有的 access token 过期时间可能只有几分钟。

另外一种办法是,再来一个 token,一个专门生成 access token 的 token,我们称为 refresh token。

1.access token 用来访问业务接口,由于有效期足够短,盗用风险小,也可以使请求方式更宽松灵活

2.refresh token 用来获取 access token,有效期可以长一些,通过独立服务和严格的请求方式增加安全性;由于不常验证,也可以如前面的 session 一样处理

upload successful
如果refresh token过期了,就只能重新登陆了。

五.session与token对比

客户端存cookie与存放于其他地方

1.出了浏览器环境之外就没有cookie了;

2.cookie是浏览器在域下自动携带的。很容易引发CSFR攻击

存放在别的地方可以解决部分问题
服务端存储数据于不存

1.存数据的话可以缩短认证字符串的长度,减小请求体积

2.不存数据就不会出现分布式处理的问题,降低硬件成本,避免查库带来的验证延迟

六.单点登录

前面我们已经知道了,在同域下的客户端/服务端认证系统中,通过客户端携带凭证,维持一段时间内的登录状态。

但当我们业务线越来越多,就会有更多业务系统分散到不同域名下,就需要「一次登录,全线通用」的能力,叫做「单点登录」。

主域名相同的情况下,就可以直接把cookie设置为主域名就可以实现了。

如果主域名不同,我们就需要独立的认证服务,称为SSO。

upload successful

  • 用户进入 A 系统,没有登录凭证(ticket),A 系统给他跳到 SSO
  • SSO 没登录过,也就没有 sso 系统下没有凭证(注意这个和前面 A ticket 是两回事),输入账号密码登录
  • SSO 账号密码验证成功,通过接口返回做两件事:一是种下 sso 系统下凭证(记录用户在 SSO 登录状态);二是下发一个 ticket
  • 客户端拿到 ticket,保存起来,带着请求系统 A 接口
  • 系统 A 校验 ticket,成功后正常处理业务请求
  • 此时用户第一次进入系统 B,没有登录凭证(ticket),B 系统给他跳到 SSO
  • SSO 登录过,系统下有凭证,不用再次登录,只需要下发 ticket
  • 客户端拿到 ticket,保存起来,带着请求系统 B 接口

如果是在浏览器之下实现,我们需要考虑其他的东西

upload successful
对浏览器来说,SSO 域下返回的数据要怎么存,才能在访问 A 的时候带上?浏览器对跨域有严格限制,cookie、localStorage 等方式都是有域限制的。

这就需要也只能由 A 提供 A 域下存储凭证的能力。一般我们是这么做的:

upload successful
图中我们通过颜色把浏览器当前所处的域名标记出来。注意图中灰底文字说明部分的变化。

  • 1.在 SSO 域下,SSO 不是通过接口把 ticket 直接返回,而是通过一个带 code 的 URL 重定向到系统 A 的接口上,这个接口通常在 A 向 SSO 注册时约定
  • 2.浏览器被重定向到 A 域下,带着 code 访问了 A 的 callback 接口,callback 接口通过 code 换取 ticket
  • 3.这个 code 不同于 ticket,code 是一次性的,暴露在 URL 中,只为了传一下换 ticket,换完就失效
  • 4.callback 接口拿到 ticket 后,在自己的域下 set cookie 成功
  • 5.在后续请求中,只需要把 cookie 中的 ticket 解析出来,去 SSO 验证就好
  • 6.访问 B 系统也是一样

七.总结

1.HTTP 是无状态的,为了维持前后请求,需要前端存储标记

2.cookie 是一种完善的标记方式,通过 HTTP 头或 js 操作,有对应的安全策略,是大多数状态管理方案的基石

3.session 是一种状态管理方案,前端通过 cookie 存储 id,后端存储数据,但后端要处理分布式问题

4.token 是另一种状态管理方案,相比于 session 不需要后端存储,数据全部存在前端,解放后端,释放灵活性

5.token 的编码技术,通常基于 base64,或增加加密算法防篡改,jwt 是一种成熟的编码方案

6.在复杂系统中,token 可通过 service token、refresh token 的分权,同时满足安全性和用户体验

7.session 和 token 的对比就是「用不用cookie」和「后端存不存」的对比

8.单点登录要求不同域下的系统「一次登录,全线通用」,通常由独立的 SSO 系统记录登录状态、下发 ticket,各业务系统配合存储和认证 ticket


阅读知乎文章:鉴权必须了解的5个兄弟:cookie、session、token、jwt、单点登录 - 知乎 (zhihu.com)


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!