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应用。

这两类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协议时与授权服务之间的通信过程,我省略了受保护资源服务和资源拥有者的角色:

我来和你分析下这个流程中的重点。

首先,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 的授权码许可流程的呢?其实,在前面几讲的基础上,我们现在理解这样的场景并不是什么难事儿。

我们仍以微信登录为例,看一下官方的流程图

看到这个图,你是不是觉得特别熟悉,跟普通的授权码流程没有区别,仍是两步走的策略:第一步换取授权码code,第二步通过授权码code换取访问令牌access_token。

这里的第三方应用,就是我们作为开发者来开发的应用,包含了移动App和Server端。我们将其“放大”得到下面这张图:

我们从这张“放大”的图中,就会发现有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吗?

欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。

精选留言

  • 青峰

    2020-07-14 00:27:52

    请问老师,如果采用第一种办法生成code_verifier,code_challenge_method=plain,那么code_verifier 的值就是 code_challenge 的值。
    这时候,不是获得了code_challenge 就可以推出 code_verifier 的值了吗?
    作者回复

    在这种code_challenge_method=plain情况下,code_verifier的值和code_challenge的值是一样的。

    我们首先要清楚PKCE的出现是为了解决,客户端如果不想使用服务端来支持,在失去了secret的保护下,怎么让OAuth 2.0 进行的更安全,实际上是为了防止授权码被截获,授权码的截获是发生在授权服务响应客户端【第一次】请求授权码的这个过程里面。

    在code_verifier的值和code_challenge的值是一样的情况下,是做了一个最基本的校验,当客户端【第二次】拿着授权码code和code_verifier来请求access_token的时候,授权服务会判断这次给的code_verifier和上次给的code_challenge值是否一致,如果不一致,拒绝返回access_token。

    code_challenge_method=plain、code_challenge_method=S256、给客户端增加一个服务端支持,这三种情况的安全等级是逐渐增高的。

    安全问题的发生是一个组合问题,安全问题的防护是一个成本问题。

    2020-07-14 13:03:09

  • Harvey

    2020-07-17 08:59:59

    用code_verifier验证code_challenge通过只能证明后一次请求和前一次是从同一个客户端发起的吧?怎么能起到app_secret证明客户端是谁是否合法的作用呢?
    作者回复


    PKCE机制可以【减轻】针对授权码截获的攻击,公共客户端固有的局限性PKCE并不能解决,所以它起不到secret的作用。

    另外,PKCE是OAuth 2.0的一个增补协议可以单独使用,也可以组合使用,比如,如果在具备保存secret的环境里面已经使用授权码流程的基础上再增加PKCE的支持,将会进一步增强授权码流程的安全性。

    2020-07-17 14:58:49

  • Mr.Robot

    2020-09-11 10:22:47

    PKCE 应该是防止code 返回的时候被hacker 截获 然后直接用code 去获取授权(在没有后台的情况下)。
    但是如果 获取code 发送请求时。code_verifier 也被截获。 hacker 也是可以通过这两个参数去后台获取token的把?
  • leros

    2020-07-16 04:08:49

    在PKCE协议下,第三方应用掌握了太多的秘密(verifier, challenge),考虑到移动终端千差万别,保证第三方应用的安全并不容易
    作者回复

    移动端类应用,目前各大开放平台都是要求必须有第三方的服务端的支持,由第三方的服务端来跟平台做交互通信。2012年10月发布了OAuth 2.0 的正式授权协议框架,也就是官方的RFC 6749,在2015年9月增补了PKCE协议,也就是官方的RFC 7636。PKCE发布的目的是为了缓解针对公开客户端的攻击,主要是解决授权码窃听的攻击。

    2020-07-16 08:33:38

  • 曙光

    2020-09-16 09:29:47

    “而恰恰在第二步换取访问令牌的时候,授权服务需要的就是 code_verifier 的值。因此,这也就避免了访问令牌被恶意换取的安全问题。” 如果截获的是访问令牌,那PKCE是不是就起不到作用了?目前我的理解,PKCE增强了code被截获的安全性,但对访问令牌被截获,无能为力。
  • 往事随风,顺其自然

    2020-07-16 16:04:12

    “迷你”的 Web 服务器嵌入到 App 里面去,这个不大理解。
    App 通过监听运行在 localhost 上的 Web 服务器 URI 这个是怎么实现的?
    作者回复

    移动App类应用不像Web应用或者浏览器应用那样可以让用户通过浏览器来访问,如果为了实现这样的方式,可以尝试的做法是需要移动App类应用能够访问操作系统上的浏览器,为了能够监听到这个前端的响应,还需要通过一个URI提供服务,可以通过一个微型的内嵌在应用内、运行在localhost上的Web服务器。

    2020-07-16 21:57:49

  • 哈德韦

    2020-07-14 09:51:12

    如果 Web 端也采用 PKCE 协议,是不是也不需要服务器端了(既纯Web前端也可以对接 OAuth 服务)?
    作者回复

    PKCE是OAuth 2.0 的一个增订”协议“,来解决【公共客户端】授权码可能遭劫持的问题。公共客户端无法保存配置时的秘钥等信息。Web应用有自己的服务端支持,可以很好的解决秘钥的保存问题,所以Web应用是不建议这样做的,而且移动App也不建议这做我们课程中给出了建议。如果你确实不需要一个服务端的移动App可以尝试这样的方式,PKCE是RFC 7636的内容。

    2020-07-14 10:30:11

  • 极客

    2021-07-08 09:36:33

    想问一下,不用oauth直接用rsa加密传输会不会更好,交互少,没有重定向。1.第三方客户端请求第三方的服务端,拿到我们公钥加密的数据,带上第三方对指定数据的签名,青叔音我们平台,2.我们平台解密验证签名,成功后用第三方的公钥加密正确的token。3第三方去自己的后台解密。只有3个流程还不用担心安全。这个会不会比oauth更好呢?不懂为啥不用这种,各个平台还搞oauth?
  • 邓文斌

    2021-03-30 18:12:14

    请教老师,假如我现在有三个自家的APP,以后可能也有第三方的,这三个APP分别是A,B,C,然后A,B,C有各自的sever端,但是用户资源在A的server端,所以得通过A的sever做登录验证,目前有个需求要做一个统一授权服务器,去给B和C颁发令牌做授权,让B和C通过A授权登录获取用户信息,用Oauth2的哪种模式合适?目前看了你的文章觉得授权码模式合适,感激!现在就是不了解这个授权服务器怎么去落地。
  • Geek_7c4953

    2020-11-23 11:05:57

    像微信APP请求自家服务端code的流程,OAuth2.0是否有提供规范呢?我个人是比较支持前后端分离的架构,但OAuth2.0的授权服务似乎都是前后端一体的。像是code模式,都是由后端重定向传递。是否有一种前后端分离的,前端调用code接口,后端返回code后再由前端传递给第三方应用的规范框架呢?就像微信那样。
  • 曙光

    2020-09-16 09:36:49

    思考题,参考PKCE,增加多一层的随机码,发给服务器校验,就能提升安全性。例如微信云服务器的MFA码,阿里云的手机验证码。
    就像一道安全门如果觉得不够安全,那就再加一道门。实际上,没有绝对的安全,这只是在增加小偷开门对成本。如果增加小偷的开门成本,其实也增加了用户的开门成本。
  • Geek_7b3867

    2020-08-11 09:38:23

    老师,在有服务器的情况下,详细的那个图中是会拉起微信进行用户授权,这样到了微信开放平台是可以验证是谁授权的;但是无服务器的情况下,生成code_verifier和code_challenge过程并未看到用户授权,这两个值传到微信后台如何判断是谁授权的?如何校验?
    作者回复

    介绍PKCE的时候是为了突出流程,就么有画出用户的角色,生成code_verifier和code_challenge都是在用户点击授权之后产生的,所有的授权流程包括之前讲的授权码等等,都是发生在用户点击了授权按钮之后,才发生的,用户的授权肯定都是在平台上面进行,所以平台一定能够拿到用户的登录态。

    2020-08-11 20:27:26

  • 我行我素

    2020-08-05 11:56:35

    请问:code_verifier在app端随机生成,服务端也不知道结果,那么怎么验证是否是合法的呢?
  • Dark

    2020-07-15 08:50:29

    code_verifier是不是就可以理解为一个随机字符串
    作者回复

    可以

    2020-07-15 11:18:54

  • Ryan Pan

    2020-07-14 08:01:52

    请问如果移动App是自家的,用资源拥有者授权的话,app secret建议存哪里呢?
    作者回复

    存在服务端,有一个专有的秘钥服务器,但只用户登录之后换回access_token然后才可以去请求或者操作【带有用户属性】的数据。

    2020-07-14 20:12:29

  • Monday

    2024-03-01 09:24:39

    作个笔记:PKCE发布的目的是为了缓解针对公开客户端的攻击,主要是解决授权码窃听的攻击。
  • Grayson

    2024-01-15 22:18:10

    老師您好,請問根據 RFC8252 這份協議中,跟 oauth 有什麼樣的差異嗎?
  • 蝴蝶

    2023-02-14 22:42:58

    我有一个疑问,在使用微信App 给目标 App 授权时,目标 App 是不是也要有提供自己的app_id?和它配套的后端服务器使用的同样的 app_id吗?或者说第三方 App 什么都不需要准备?只需要后端服务器在微信开放平台申请了 app_id和 app_secret?
  • 程序员花卷

    2023-02-08 14:48:43

    微信小程序登录后获取access_token时使用的是 "客户端模式",没有用到code
  • eggsy

    2022-12-05 11:48:19

    请问,如果没有server的app,那么使用资源拥有者凭据这种方式不行吗?