先后端鉴权二三事

DevUI是一支兼具设计视角和工程视角的团队,服务于华为云 DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师。
官方网站: devui.design
Ng组件库: ng-devui(欢迎Star)
官方交流群:添加DevUI小助手(微信号:devui-official)进群
DevUIHelper插件:DevUIHelper-LSP(欢迎Star)

引言

先后端鉴权是一个很大的话题,不一样组织的鉴权方式各不相同,甚至对同一协议的业务实现也可能相去甚远。本文尝试从认证与受权两个维度来描述标题中的鉴权,大部分篇幅仍是偏认证。文章主要包含三部份内容:区分认证与受权、常见的认证及受权方式和企业应用中常见的单点登陆(SSO)方案。html

1 认证与受权

首先,咱们来简单看一下认证与受权,并理清楚二者之间的区别。前端

认证(Authentication)

认证涉及一方应用和一方用户,用于描述用户在该应用下的身份。认证能够简单理解为登陆,以此确认你是一个合法的用户。好比说掘金必需要登陆才能点赞、收藏。git

受权(Authorisation)

受权涉及两方应用和一方用户,用于描述第三方应用有哪些操做权限。github

带入场景区分认证与受权

咱们分别举三个例子来讲明三种状况让你们对认证和受权的关系有更好的理解:只认证不受权、即认证又受权、不认证只受权。web

(1)只认证不受权

上面提到的使用掘金帐号登陆掘金就是只认证不受权的场景,此时掘金只知道你是哪一个用户,可是不涉及到受权的操做。ajax

(2)即认证又受权

一样是登陆掘金,咱们能够不使用在掘金注册的帐号和密码登陆,而选择第三方应用登陆,好比说github 帐号。此时会弹出github 的登陆页面,若是你在此页面输入帐号和密码进行登陆,则至关于默认受权给掘金获取你的github 的头像和帐号名。在这个过程当中即完成了认证(合法用户)又完成了受权(你容许掘金从github 获取你的信息)。json

(3)不认证只受权

以某外卖小程序为例,在你第一次进入外卖小程序的时候小程序会弹框请求获取你的我的信息,此时至关于上面提到的即认证又受权。你赞成之后就至关于使用微信帐号登陆,可是此时外卖小程序获取到的你的信息不包括你的手机号。当你要下单点击提交的时候,小程序再次发起请求,要获取你微信绑定的手机号,此时发生的动做就是不认证只受权。小程序

2 有哪些经常使用的认证和受权方式?

一旦涉及认证,必需要考虑的一个问题就是状态管理。所谓的状态管理就是说咱们在一个网站进行登陆以后的一段时间里,不但愿每次访问它都须要从新登陆,因此应用开发者必需要考虑怎么样保持用户的登陆状态以及决定什么时候失效。而这个过程须要先后端通力合做来完成。下面介绍几种常见的认证和受权方式。c#

Session-Cookie 认证

(1)流程

Session-Cookie 的认证流程以下:用户先使用用户名和密码登陆,登陆完成后后端将用户信息存在session 中,把sessionId 写到前端的cookie 中,后面每次操做带着cookie 去后端,只要后端判断sessionId 没问题且没过时就不须要再次登陆。segmentfault

使用这种方式进行认证,开发者可能面临的主要问题以下:

  • cookie 安全性问题,攻击者能够经过xss 获取cookie 中的sessinId,使用 httpOnly 在必定程度上提升安全性
  • cookie 不能跨域传输
  • session 存储在服务器中,因此session 过多会耗费较大服务器资源
  • 分布式下session 共享问题

Token 认证

与上面的Session-Cookie 机制不一样的地方在于,基于token 的用户认证是一种服务端无状态的认证方式,服务端能够不用存放token 数据,可是服务器能够验证token 的合法性和有效性。使用token 进行认证的方式这里主要介绍两种:SAML 和JWT.

SAML (Security Assertion Markup Language)

SAML 的流程以下:

  • 未登陆的用户经过浏览器访问资源网站(Service Provider,简称SP)
  • SP 发现用户未登陆,将页面重定向至IdP(Identity Provider)
  • IdP 验证请求无误后,提供表单让用户进行登陆
  • 用户登陆成功后,IdP 生成并发送SAML token (一个很大的XML对象) 给SP
  • SP 对token 进行验证,解析获取用户信息,容许用户访问相关资源

针对上面的流程补充两点信息:

(1)SP 是如何验证token 的合法性?

好比是否有可能token 在IDP 到SP 的过程当中被人劫持并修改了内容?

答案是:没有可能。由于IDP 返回给SP 的token 使用IDP 的私钥进行了签名,而经过私钥签名后的信息能够经过对应的公钥进行验证。

(2)SP 如何判断token 是否过时?

SAML token 携带了token 过时时间的信息。

(3)生成的SAML token 是托管在SP 仍是前端?

若是是托管在SP,那么又要引入session 机制,若是托管在前端,那么前端须要存储而且每次传递SAML token,可是SAML token 大小又比较大,耗费传输资源。

答案是:均可以。放在前端的话须要前端经过单独的ajax 请求获取token 并存储在localStorage 或者其余的本地存储中。若是是托管在SP,那么就像上面说的,引入session,前端只掌握sessionId,这样的话token 机制其实就退化成了上面提到的session-cookie 机制。

JWT(JSON Web Token)

关于JWT 的文章有不少,这里再也不赘述,相关信息能够参考阮一峰老师的入门文章:JSON Web Token 入门教程(预计阅读时间:2mins)

简言之,JWT 就是一种在用户登陆后生成token 并把token 放在前端,后端不须要维护用户的状态信息可是能够验证token 有效性的认证及状态管理方式。

文章里已经有的内容这里不过多探讨,想聊一聊的是在此基础之上延伸出的一个问题:

JWT 用于签名和验证签名的secret 对于全部人来讲都是同样的吗?

若是同样的则存在比较大的安全隐患,一旦泄露,全部JWT 均可能会被破解。若是不同,那么一样须要在服务器端维护每个人对应的secret 信息,这样的话和服务器端维护session 信息又有什么区别呢?

从JWT官方Introduction 的介绍文档中看到这样一句话:

The signature is used to verify the message wasn't changed along the way, and, in the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.

也就是说,secret 能够用服务器私钥。若是这样的话,对于全部用户都是同样的。若是服务器私钥丢了,那全部的安全都无从谈起,因此JWT 就是假设这个私钥不会丢。固然按个人理解,开发者也能够为每一个用户设置一个单独的secret,这就必需要面临上面提到的复杂性问题了。

关于JWT 和SAML 的对比,有一张颇有意思的图片

OAuth 受权

OAuth 的设计本意更倾向于受权而不是认证,因此这一小节的标题写的是受权,可是其实在受权的同时也已经完成了认证。

本文偏向于认证,OAuth 在这里不过多讨论,更多OAuth 内容能够参考这篇:理解OAuth 2.0

3 SSO 与CAS

接下来咱们探讨一个企业应必定绕不过的课题:单点登陆。

举例来讲,华为云下有若干云服务。包含项目管理、代码托管、代码检查、流水线、编译构建、部署、自动化测试等众多微服务的DevCloud(软件开发云) 正是其中之一,用户若是在使用任意一个服务没有登陆的时候均可以去同一个地方进行登陆认证,登陆以后的一段时间内能够无需登陆访问全部其余服务。

在单点登陆领域,CAS(Central Authentication Service,中文名是中央认证服务) 是一个被高频使用的解决方案。所以,这里介绍一下利用CAS 实现SSO。而CAS 的具体实现又能够依赖不少种协议,好比OpenID、OAuth、SAML 等,这里重点介绍一下CAS 协议。

CAS 协议中的几个重要概念

简单介绍一下CAS 协议中的几个重要概念,一开始看概念可能很模糊,能够先过一遍,再结合下面的流程图来理解。

  • CAS Server:用于认证的中心服务器
  • CAS Clients:保护CAS 应用,一旦有未认证的用户访问,重定向至CAS Server 进行认证
  • TGT & TGC:用户认证以后,CAS Server 回生成一个包含用户信息的TGT (Ticket Granting Ticket) 并向浏览器写一个cookie(TGC,Ticket Granting Cookie),有啥用后面流程会讲到
  • ST:在url 上做为参数传输的ticket,受保护应用能够凭借这个ticket 去CAS Server 确认用户的认证是否合法

CAS 协议核心流程

介绍完概念,结合官方给出的流程图(先耐心地把图看一遍再看后面的流程解析效果更佳),对每一步进行详细的拆解,并点出几个可能会让人感到疑惑的问题。

① 用户经过浏览器访问受保护应用(如下简称app_1)首页

② app_1 侧的CAS Client 经过检测session 的方式察觉到到用户未进行过认证,将用户重定向(第一次重定向)到CAS Server,url 上携带的参数service 包含了app_1 的访问地址

③ CAS Server 检测到用户浏览器没有TGC,提供表单让用户登陆,用户登陆成功后,CAS Server 生成包含用户信息的TGT,并写TGC 到用户浏览器

  • TGC 跟TGT 相关联,是用户浏览器直接向CAS Serv er 获取ST 的票据,若是TGC 有效,用户就不须要完成表单信息填写的步骤直接实现登陆
  • TGC 的过时策略是这样设置的,若是用户一直没有页面操做和后台接口请求,那么默认2 小时过时,若是一直有操做,默认8 小时过时,开发者能够在cas.properties 中对这两个过时时间进行修改,通常的应用中不会配置这么长的过时时间
# most-recently-used expiration policy
cas.ticket.tgt.timeout.maxTimeToLiveInSeconds=7200
# hard timeout policy
cas.ticket.tgt.timeout.hard.maxTimeToLiveInSeconds=28000

④ CAS Server 把浏览器重定向(第二次重定向)回app_1 首页,此时重定向的url 携带了ST

⑤ app_1 再次接收到用户浏览器的访问,把上一步url 参数中的ST 拿出来,凭着ST 去CAS Server 确认当前用户是否已经完成认证,CAS Server 给出确定回复之后,app_1 拿掉url 上的ST 重定向(第三次重定向)浏览器至app_1 首页

  • app_1(CAS Client)凭借ST 去向CAS Server 确认当前用户认证状态的同时获取了包含用户信息在内的额外信息
  • 把这些额外信息写到session 里并把sessionId 返回给前端,那么前端下一次访问的时候直接判断session 是否有效就能够了

⑥ 用户端浏览器去访问同一认证体系下的app_2 首页

⑦ 同第②步,app_2 侧的CAS Client 经过检测session 的方式察觉到到用户未进行过认证,将用户重定向到CAS Server,url 上携带的参数service 包含了app_2 的访问地址

⑧ CAS Server 检测到用户浏览器的TGC,找到对应的TGT,经验证是合法的,此处呼应了第③步的TGC

⑨ 同第④步,CAS Server 把浏览器重定向回app_2 首页,此时重定向的url 携带了ST

⑩ 同第⑤步,app_2 再次接收到用户浏览器的访问,把上一步url 参数中的ST 拿出来,凭着ST 去CAS Server 确认当前用户是否已经完成认证,CAS Server 给出确定回复之后,app_2 拿掉url 上的ST 重定向浏览器至app_2 首页

关于CAS 流程中的几个问题

(1)如何避免sessionId 冲突?

业务服务器(咱们不妨把它当作跟先后文中提到的CAS Client是一个东西)经过在服务端写session 而且把sessionId 传回给前端保存的方式,保证用户登陆的一段时间内不须要再次登陆。那么如何保证使用同一单点认证的各个子服务(下文以服务a 和服务b 来举例描述)的的sessionId 不冲突?固然这个问题的前提是服务a 和服务b 没有使用共享session 的状况。

若是服务a 和服务b 使用了共享session,那么他们的sessionId 确定是一致的,即二者的CAS Client 在上述流程②中检测的session 是一致的。此时若是用户已经在服务a 登陆,那么能够直接访问服务b,由于在第②步的时候登陆状态就已经验证经过。

若是服务a 和服务b 没有使用共享session,那么用户在服务a 登陆以后,再去访问服务b,要走上面流程中的第⑧步才能够确认用户是登陆过的,此时,用户仍然不须要登陆就能够访问,可是验证流程相比于共享session 要长不少,若是你去观察network 中的全部请求,也会发如今这个过程当中多了几个302。此处要讨论的问题偏偏是在这种状况下a 和b 如何避免sessionId 冲突。

一旦发生冲突,就会致使用户在a 和b 之间切换的时候,双方的CAS Client 须要不断地去CAS Server 确认并刷新session。这一段的描述若是不太好理解,能够往上翻一翻再看一遍上面流程图中的【First Access To Second Application】部分。其实要避免冲突也很简单,即a 和b 各自在本身写入前端cookie 的key 上加入服务名做为前缀,好比分别写成a_sessionId 和b_sessionId。

(2)假设a 与b 使用一样的单点登陆认证Server,有没有可能出现a 应用登陆过时,b 应用没有过时的状况?

接着上面的问题进行讨论,a 和b 在不使用共享session 的状况下,有没有可能出现这样一个状况:a 应用的认证状态过时了(session 和TGC 都无效)而b 应用仍没有过时(session 或TGC 至少存在一个有效)?

答案是:不会。在业务实现中,CAS Client 会按期和CAS Server 进行通讯,若是用户一直在操做,那么CAS Server 就会相应延长TGC 的过时时间,最终对于a 和b 来讲,TGC 的过时时间必定是相同的。因此哪怕两边的session 设置过时时长不一致,认证状态最多走到CAS Server 处经过TGC 的检测就能完成,而不会出现a 须要登陆,b 不须要登陆的状况。

总结

本文首先探讨了认证与受权的区别,并列举了几种常见的认证与受权方式。而后重点介绍了一下使用CAS 协议实现单点登陆的流程与问题。最后,补充一点。华为云DevCldoud 的CAS Client 正是参考标准的CAS 协议实现,感兴趣的同窗能够在这里注册一个帐号,而后打开F12 使用帐号登陆观察全部的网络请求并分析一下CAS 业务实现的完整流程。

加入咱们

咱们是DevUI团队,欢迎来这里和咱们一块儿打造优雅高效的人机设计/研发体系。招聘邮箱:muyang2@huawei.com。


文/DevUI 少东

参考文章

往期文章推荐

《好用到飞起!VSCode插件DevUIHelper设计开发全攻略(一)》

《Web界面深色模式和主题化开发》

《手把手教你搭建一个灰度发布环境》