1. 写在前面的话
我之前参与过一个OpenAPI
的项目,其业务形态非常简单,一句话就可以说清楚:第三方应用想要来调用我们系统的接口,以获取我们的内部数据。OpenAPI
项目最关键的一点,就是要解决对外接口的安全性问题,比如最基础的:
- 谁能调用我们系统的接口?
- 他有权限调用哪些接口?
在技术选型的阶段,我们阅读了一些国内做OpenAPI
的厂商的公开资料,很自然的就找到了 OAuth2.0
这个主流的授权框架,但当我搜索相关资料尝试弄明白这个框架时,却遇到了问题:很多关于OAuth2.0
的博客都偏理论,而且内容高度雷同,了解概念有余,但想依靠这些信息来搭建一套安全的OpenAPI
平台还是有所不足。为此,我们团队又花了很多时间去阅读OAuth2.0
相关的规范文档,对我们比较有帮助的文档如下:
- 理论基础:RCF 6749《The OAuth 2.0 Authorization Framework》
- 实践支撑:Financial-grade API (FAPI) Final Specifications
也是在做OpenAPI
项目的期间,对OAuth2.0
有了一些比较粗浅的理解,这里整理记录下来,以作回顾。
本篇是该系列的第一篇,主要介绍OAuth2.0
要解决什么问题。
2. 认证授权就是用户名密码?
我在做OpenAPI
项目之前,一直觉得认证授权是一件很简单的事情,就像宋丹丹要把大象装冰箱一样,总共只需要三步:
- 用户在浏览器中输入用户名和密码,点击登录;
- 服务端接收到用户名和密码信息,校验其正确性,成功则返回登录令牌;
- 浏览器接收到登录令牌,后续向服务端发送的请求都会携带该登录令牌用于身份校验;
流程大致如下图所示:
是吧?相信很多人跟我一样,一提到认证授权的时候,脑子里第一时间想到的流程就是这样的。但我转念一想,不对啊,这三步顶多也就百来行代码能解决的事情,为什么有的公司专门招了几十号人在做认证授权系统,甚至还有一些公司靠提供 IDaas 的服务做到几十亿市值?
奇怪,资本家不可能有错,有错的一定是我自己。所以我们扩展一点来想,除了使用用户名密码登录这种最常见的认证授权场景外,还有哪些场景是值得我们探究的?
3. 名词统一
在开脑洞前,我们先来归纳一下上面这个最简单的认证授权场景里的一些主体,统一一下名词概念,避免我说的跟你理解的不一致,同时也提前跟OAuth2.0
中的一些概念对齐。
- 用户 ->
EndUser
:即最终使用系统的人,如你和我; - 浏览器 ->
UserAgent
:用户通过 UserAgent 跟我们的系统打交道,Web 系统最常见的 UserAgent 当然是浏览器,也有一些 APP 通过 WebView 套壳能实现类似的能力; - 认证要素 ->
Credencials
:认证要素,用于识别用户的身份。最常见要素是用户名密码,其它诸如指纹、人脸等生物特征也得到了广泛的应用; - 登录令牌 ->
Token
:一段安全生成的、随机且唯一的字符串,用作用户登录后的凭据。比如我们都很熟悉的SessionID,就是登录令牌的一种典型表现; - 服务端 ->
Server
:用户所属系统的服务端,存有用户的认证要素、数据等敏感信息;
按名词填充后的流程图:
4. 数据安全等级和开放系统
4.1. 构造基础系统
我们来尝试构造一个系统,用于解释后续的各类场景。我们以手机银行 APP
为例,假设该系统具有以下四个接口:
序号 | 接口 | 数据安全等级 |
---|---|---|
1 | 查询银行网点 | 不涉及敏感信息,对任何人均可见 |
2 | 查询银行理财产品 | 需要登录才能查看,但不涉及用户级敏感数据 |
3 | 查询用户账户列表和余额 | 需登录才能查看,涉及用户级敏感数据,仅用户本人可见 |
4 | 跨行转账 | 需登录及二次验证,涉及用户级敏感数据,仅用户本人可操作 |
4.2. 数据安全等级对认证授权的影响
我们考虑下,如果我们就是这家银行的开发人员,如果要保证这几个接口的安全性,可以实现吗?
答案是当然可以。基于现有经验,很简单对不对?
- 用户打开
手机银行 APP
后,在 APP 首页的某个地方可以看到查询银行网点的按钮,点击后即可查询到附近的网点信息,无需用户登录; - 当用户点击理财产品页的时候,由于理财产品列表的接口不是对所有人可见的,这时候
手机银行 APP
会先弹出登录界面,等用户输入认证要素(用户名密码、指纹、人脸等)登录完成后,才能看到所有理财产品列表; - 当用户要查询账号列表和余额时,除一定要登陆外,还可以看到账号或者余额的一部分信息会以星号显示(如账号会显示为 6225****0000),这是银行为防止用户敏感信息泄露,采取了数据脱敏处理;
- 当用户需要进行跨行转账操作时,除一定要登录外,还需要额外通过手机验证码、交易密码、指纹等方式进行二次认证,成功后才可以进行本次转账操作;
我们可以看到,对于手机银行 APP
而言,即便是同一系统的不同接口,由于接口涉及的数据的安全等级不同,需要采取的安全策略也不同,数据安全等级要求越高,对相关接口的采取的安全策略就会越复杂。
4.3. 开放系统间授权
我们再来进行一些扩展,考虑当手机银行 APP
需要跟其他第三方应用产生交互的场景。
- 场景一:假设
公司A
推出一款理财 APP
,想要调用我们手机银行 APP
的“查询银行理财产品”的接口,应该怎么实现? - 场景二:还是这款
理财 APP
,还想调用我们手机银行 APP
的“查询用户账户列表和余额”接口,应该怎么实现,跟场景一有什么区别? - 场景三:假设
公司B
推出一款个人资产概览
的小程序,其业务很简单,就是查询用户在各家银行的账户余额,将余额汇总计算后展示给用户。该小程序的特点是没有自己的后台系统,仅通过前端的JavaScript代码调用各银行的“查询用户账户列表和余额”接口即可,应该怎么实现?
按照之前的设定,我们是手机银行 APP
的开发人员,现在产品找到我们并要求我们能支持上述三个场景。接下来,我们来进行头脑风暴,看看这三个场景分别有什么特点,是需要我们注意的。
4.3.1. 场景一讨论区
- 程序员小A:如果
公司A
的理财 APP
要调用我们的“查询银行理财产品”的接口,这个接口是需要鉴权的,我们现有手机银行 APP
的认证授权使用的是用户名密码的认证要素,要不然我们也把公司A
作为一个普通的用户来处理,给他分配用户名密码,让理财 APP
也以这种模式登录,认证完成之后就可以以普通用户的身份来调用“查询银行理财产品”接口了,这样可以吗? - 架构师大B:这是一种比较自然的解决思路,听起来是能实现功能的,但放在我们
手机银行 APP
的场景下似乎无法落地,你想想为什么? - 程序员小A:嗯…我知道了,普通用户在开通我们手机银行之前,需要先办理银行卡,在场景一里,
公司A
只是想调用理财相关的接口,强制他去办卡似乎不太合理…
架构师大B:没错,你抓到了一个重点。除此之外,还会对其他业务有影响吗? - 程序员小A:嗯…我再想想,即便要求
公司A
去办卡,也成功开通手机银行 APP
,但公司A
因为业务原因,其关联的手机银行用户长期都不会有动账交易(取款、转账、缴费等),这可能会被我们系统识别为僵尸用户,最终被冻结处理。 - 架构师大B:是的,类似这样的情况其实还很多,原因是
公司A
的理财 APP
本质上并不是一个真实的用户,但在你的方案里将他们混为一谈了,看似“巧妙”的解决了鉴权的问题,实则会引发一系列的业务矛盾。 - 程序员小A:您说得对,那我们应该如何做?
- 架构师大B:很简单,将普通用户和第三方应用概念上拆开。对普通用户,我们仍然存放入
user
表,分配 username 和 password 用于鉴权;对公司A
的理财 APP
这样的第三方应用,我们存放入client
表,分配client_id
和client_secret
用于鉴权。 - 程序员小A:好的,我理解了,这样在我们系统中就可以将第三方应用独立处理了,不会影响到普通用户的逻辑。
4.3.2. 场景二讨论区
- 程序员小A:咦,产品是不是搞错了,场景二和场景一不是一样的吗,为什么要单独提需求?我去找他去。
- 架构师大B:别,你先坐下,场景二里
公司A
的理财 APP
要调用的是“查询用户账户列表和余额”接口,你仔细想想,这个接口跟之前的“查询银行理财产品”有什么区别吗? - 程序员小A:哦,你提醒我了,我知道了,这个接口涉及用户敏感数据!!!
- 架构师大B:是的,还好你想起来了,不管我们做任何系统,一定要谨记,对于用户敏感的数据,虽然是存放在我们
手机银行 APP
的数据库中,但我们并没有权限绕开用户将这些数据直接提供给公司A
使用,这是违法的。在将这些用户敏感数据提供给第三方公司使用时,必须要先征得用户同意
,你知道什么是用户同意
吗? - 程序员小A:这个我知道,就好比我在王者荣耀中想要向游戏好友展示我的微信头像,但是对于微信而言,微信头像和用户的公开信息是属于用户的数据,微信没有权限直接将这些信息提供给王者荣耀使用。所以在流程上,王者荣耀 APP 会先唤起微信 APP,微信 APP 会弹出一个用户告知的界面,告诉我王者荣耀想要使用我的微信头像,问我允不允许,这个征询的过程,就是“用户同意”。
- 架构师大B:没错,没想到你连这个也知道,真是让我刮目相看!
4.3.3. 场景三讨论区
- 程序员小A:场景一和场景二算是理解了,但场景三这个需求我又有点懵了,不都是调用“查询用户账户列表和余额”接口吗,和场景二难道有区别?
- 架构师大B:是的,有区别,但这个区别不在我们,在他们,你想想为什么?
- 程序员小A:他们?我能看出来,这个
公司B
的个人资产概览
小程序有点特殊,他没有后台,是纯H5的应用,但这对我们而言能有什么影响呢? - 架构师大B:会有影响,你结合场景一和场景二想一想,
公司B
的个人资产概览
小程序对我们手机银行 APP 而言,他是算user
(普通用户)还是client
(第三方应用)? - 程序员小A:这个小程序需要查我们系统很多普通用户的账户余额,显然应该算是
client
。 - 架构师大B:对,对于
client
而言,我们会给他分配client_id
和client_secret
用于鉴权,没错吧,你想想对于公司B
的个人资产概览
小程序而言,他怎么实现这个鉴权的功能? - 程序员小A:哦…我知道了,对于
公司A
的理财 APP
,这个client_id
和client_secret
可以存储在他的后端服务器上,这样是可以保证这个认证要素的安全性。但对于公司B
的个人资产概览
小程序,他是没有后端服务器的纯H5的应用,他没有任何手段可以让这个认证要素不泄露出去(PS:H5应用运行在浏览器上,如果将client_id
和client_secret
硬编码在JavaScript代码里,是可以轻易被任何人获取到的,所以无法保证其安全性)! - 架构师大B:不错,脑子转的挺快的,就是这个原因。所以你看,想要将我们
手机银行 APP
的接口安全的开放给第三方应用使用,并不是这么容易的事情。不仅需要考虑接口的数据特征,比如是否涉及用户敏感数据等,还需要考虑第三方应用的特征,比如他是否有能力保障认证要素的安全性,等等。 - 程序员小A:是的,现在想起来,我一开始想用用户名密码来解决所有这些场景的想法,实在是太粗浅了。那对于这些问题,有什么一劳永逸的解决方案吗?
- 架构师大B:当然,用
OAuth2.0
!!!
5. 名词再次统一
讨论到这里,我们引入了一个重要的主体,就是client
,这个主体在OAuth2.0
中非常重要,所以我们单独提出来,加深印象。
- 第三方应用 ->
Client
:即需要调用资源所有者系统接口的第三方应用;Confidential Clients
:私密应用,能够保障其凭证的保密性(在安全的服务器上部署的应用,对其凭证的访问受到限制。如公司A
的理财 APP
,其凭据可以存放在后端服务器上,相对安全);Public Clients
:公开应用,无法保障其凭证的保密性(本地应用或基于 web 浏览器的应用。如公司B
的个人资产概览
小程序,其凭据只能存放在前端,非常不安全);
6. 小结
花了很大的篇幅,我们终于意识到由于数据安全等级不同、开放系统间授权场景不同,对认证授权系统也提出了更多的要求。传统意义上的用户名密码并不是银弹,他没有办法解决上述场景下的各种问题,但同时我们也明确的知道,上述的问题并非是无解的,对于这些问题的解,就是OAuth2.0
。
所以再下一篇文章中,我们就要进入正题,聊聊复杂场景下的授权体系:OAuth2.0
。
如果觉得有帮助,请关注我的微信公众号