You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

138 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 07 | 如何在移动App中使用OAuth 2.0
你好,我是王新栋。
在前面几讲中我都是基于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应用。
![](https://static001.geekbang.org/resource/image/4c/99/4c034e019467aafae511f16055b57b99.png "图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协议时与授权服务之间的通信过程我省略了受保护资源服务和资源拥有者的角色
![](https://static001.geekbang.org/resource/image/66/52/66648bff2d955b3d714ce597299fbf52.png "图2 使用PKCE协议的流程图")
我来和你分析下这个流程中的重点。
首先App自己要生成一个随机的、长度在43~128字符之间的、参数为**code\_verifier**的字符串验证码;接着,我们再利用这个**code\_verifier**来生成一个被称为“挑战码”的参数**code\_challenge**。
那怎么生成这个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)
![](https://static001.geekbang.org/resource/image/86/b1/86d3yy8fa419c94b7e3766fe0a4e3db1.png "图3 微信登录流程图")
看到这个图你是不是觉得特别熟悉跟普通的授权码流程没有区别仍是两步走的策略第一步换取授权码code第二步通过授权码code换取访问令牌access\_token。
这里的第三方应用就是我们作为开发者来开发的应用包含了移动App和Server端。我们将其“放大”得到下面这张图
![](https://static001.geekbang.org/resource/image/56/5e/564f5b7af360180d270e205df5f9c05e.png "图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保护的时候防止授权码失窃的解决方案。
2. 我们需要思考一下我们的App真的不需要一个Server端吗我建议你在开发移动App的时候尽可能地都要搭建一个Server端因为通过后端通信来传输访问令牌比通过前端通信传输要安全得多。我也举了微信的例子很多官方的开放平台在提供OAuth 2.0服务的时候都会建议开发者要有一个相应的Server端。
那么关于OAuth 2.0 的使用还有哪些安全方面的防范措施是我们要注意的呢,接下来的一讲中我们会重点跟大家介绍。
## 思考题
在移动App中你还能想到有哪些相对安全的方式来使用OAuth 2.0吗?
欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。