目的
最近负责的 PaaS 平台,对接了不少服务商的开发者,大家在对接授权流程的过程中,会遇到不少问题,经历不少挫折后才能成功接入。
当然,主要原因是我们的系统不友善,给广大开发者挖坑了,我们会继续想办法不断完善。
有句名言,如果对 OAuth2.0 没有足够的理解,在设计授权系统的任务上,很可能不自觉就设计出一个劣化版的 OAuth2.0 轮子。
在这篇文章中,希望通过对 OAuth2.0 的深入思考,加深原理的理解,在 OAuth2.0 的流程上少踩坑。以后在设计授权流程的过程中,也能减少冗余设计。
提前说明
本文更多讲述思考过程,不会事无巨细地讲述 OAuth2.0 的方案流程。
有需要了解 OAuth2.0 方案描述的同学,欢迎阅读 rfc6749
思考过程
有四种模式:授权码模式,简化模式,密码模式,客户端模式
其中,简化模式、密码模式、客户端模式 最安全的方式,只能说是提供了这么一套可行的授权流程
日常中,除开内部系统的小轮子,我们用到的大多数是 oAuth 2.0 中的 授权码模式
授权码模式 旨在 主要解决 信息互相信任的问题:
- 用户:我向 哪个服务商 授权 什么信息
- 服务商:哪个用户 授权了 什么信息 给我
- 授权方:谁 向我申请授权信息给 哪个服务商
(服务商可以理解成是 网页应用的提供方)
针对上面的描述,我们可以总结出,每一方都 缺乏了 两个重要信息,我们需要怎么保证每一方的两个重要信息能够安全准确地抵达到需要的一方呢?
接下来,我们对 三方 和 相关的信息进行编号标记
用户(user):我向 哪个服务商(a1) 授权 什么信息(a2); 服务商(corp):哪个用户(b1) 授权了 什么信息(b2) 给我; 授权方(auth):谁(c1) 向我申请授权信息给 哪个服务商(c2);
标记完毕后,我们看看相关信息之前的关联:
user 可以提供 b1 和 c1
corp 可以提供 a1 和 c2
auth 可以提供 a2 和 b2
b1 和 c1 等价,a1 和 c2 等价,a2 和 b2 等价
抽象的关键信息只有三个:
- 哪个用户
- 哪个服务商
- 什么授权信息
三方互相依赖,互相提供关键信息,流程要怎么设计呢?
OAuth2.0 已经有了完备的方案,那么接下来,我们带上我们的问题剖析一下 oAuth2.0 的授权码模式过程
上述流程图来源于:tools.ietf.org/html/rfc674…
以 github 的登录授权为例进行说明(参考文章:juejin.cn/post/684490…
(A). 用户(Resource Owner)在用户带来(User-Agent,如 web 浏览器,app)上选择了第三方应用(如 github)来进行登录,会重定向到 github 授权点;
(B). 页面跳转后,github 会要求用户登录,然后询问是否给予客户端授权,用户点击同意;
(C). github 返回授权码(Authorization Code)返回给 redirect_url (重定向url);
(D). 客户端(Client)通过 URL 中取出授权码之后,就可以在后端向 github 的服务器请求令牌;
(E). github 返回 AccessToken,看需求额外返回 RefreshToken;
可以看到,标准的流程,有 5 个步骤,它们都有什么意义呢? 有可能省略吗?
我们结合之前的信息思考点,继续分析:
-
步骤 A 和 步骤 B 完成了信息 a1、a2、c1、c2 必要的交互,完成了 user 与 auth 的互相关联,user 与 corp 的互相关联;
-
步骤 C 的 授权码 提供了信息 a2 和 b2, redirect_url 提供了信息 a1 和 c2,完成了 auth 与 corp 的互相关联;
-
步骤 D 和 步骤 E 中,最后的 AccessToken 提供了信息 b1 和 b2;
分析:
步骤 A 到 步骤 C,已经完成了三方的互相授权信任;
步骤 D 和 步骤 E,目的在于能够在一段时间内,持续为 服务商提供信息 b1 和 b2,直到超时失效,失效前不需要再次执行 步骤 A 到 步骤 C;
通过分析,所有步骤都为整个流程提供了最关键的信息,从而最后保证了三方关系的建立;
继续思考,如何保证 授权机构 只授权给 它信任的 服务商 呢?
继续以 github 的 oauth 流程为例
可以看到,在步骤 A 中,有个重要参数:client_id
通过文档可知,client_id 是服务商在第三方授权机构(github)注册应用时,第三方授权机构颁发给服务商的唯一 id
继续看步骤 D 需要的参数
比步骤 A 多了一个重要参数:client_secret
无论是 client_id 还是 client_secret,它们都是 服务商在 github 注册应用时,github 颁发给服务商的
通过 client_id 和 client_secret,帮助服务商在 步骤 A 和 步骤 D 中,保证授权机构 以及 合法服务商的信任桥梁
继续思考,已经有了 client_id,为什么还要有 client_secret 呢?
这个问题涉及到 安全保障 的范畴
首先,我们知道,步骤 A 中,只需要传 client_id,就能告知 授权机构 c2 信息,而到了步骤 D,就需要 client_id 和 client_secret 一起传,授权机构才能信任获得的 c2 信息
我们仔细观察,发现 步骤 A 的请求,是从 服务商的客户端发出的,而 步骤 D 的请求,是从 服务商的服务器 发出的
至此,我们可以这么理解,OAuth2.0 授权码模式的第一个步骤,必须由服务商的客户端发起,而客户端发起的请求 有可能基于弱安全性的网络环境,但又必须携带一个信息能够关联 授权机构 与 服务商,所以 OAuth2.0 就设计了 client_id 这个参数,在步骤 A 上带上,同时表明,不能完全信任 client_id
由于不能完全信任 client_id,所以,步骤 C 返回的授权码(Authorization Code)并不能直接保存下来,然后向 授权机构 获取相关的信息
还需要 服务商的服务器 拿到授权码后,结合上 client_id 以及 只保存在服务器上的 client_secret,基于安全的网络环境(相对于客户端的不可控网络环境),单独再发请求(对应步骤 D)到 授权机构,以获取 AccessToken ,完成整体的授权过程
总结
OAuth2.0 的授权码模式描述的步骤为三方授权提供了所有的必要信息,为三方的互相信任提供了最基础,而且是最必须的步骤。
其中,client_id、client_secret、Authorization Code、AccessToken 几个参数在整个流程中提供了相互信任的安全凭证。
最后,纯属个人见解,如有偏差,欢迎讨论斧正。