前言
总所周知,OAuth 2.0 设计之初的应用场景就是针对 Web 应用,但 OAuth 2.0 并不是把自己框到某一点上,而是将自己的核心协议定位成了一个框架。也正因此,我们可以基于这个基本的框架协议,在一些特定领域进行扩展,实现不同的业务。
延申到桌面端或者 APP 端的场景下,OAuth 2.0协议一样可以提供完美的支持。
就像前文(什么是 OAuth 2.0 ?)介绍,可以通过隐式授权或者密码授权的方式实现 APP 端的 OAuth 2.0 接入,但毕竟这两组授权方式从安全性上考虑要次于授权码模式。
APP 的两种架构
当前的 APP 端又分两大阵营:无 Server 端的“纯 APP”架构(原生 APP 或者 H5 混合开发的 APP)和有 Server 端的架构。这两种架构的 APP, 在使用 OAuth 2.0 时最大的区别就在于获取令牌的方式:
- 有 Server 端:建议通过 Server 端和授权服务端交互获取令牌
- 无 Server 端:隐式授权或者密码授权(不建议)
ok,既然无 Server 端普通的授权方式不安全,那么能不能用 OAuth 2.0 中最安全的授权码模式呢?当然可以!
我们前面说到,授权码模式通过 code 换取 token 时,必须要发送 app secret,但是 app secret 属于用户的绝对私密信息,对于无服务端的 APP 来说,不可能直接将 app secret 存放到前端(APP 端),这是非常非常不明智的做法。那么能不能不传 app secret 呢?
对于这个问题,rfc6749 已经告诉我们答案了:不可以。那么问题又来了,APP 端即不能存 app secret,OAuth 2.0 授权码流程里又不能不传 secret,那不是没有解决方案了么?非也非也,这就到了本文要讲解的主题:PKCE(全称: Proof Key for Code Exchange)。
PKCE 流程
PKCE 的流程图示意如下:

参考 rfc7636 文档说明,客户端在每次授权前首先要创建一个 code_verifier
,其中 code_verifier
为一个字符串,由 43128 个字符组成,注意该值一定要是一个全局唯一的随机字符串,尽量不要存在重复的情况。
接下来通过 code_verifier
生成 code_challenge
,规则如下:
if ('code_challenge_method' == 'plain') {
code_challenge = code_verifier
}
if ('code_challenge_method' == 'S256') {
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
}
当 code_challenge_method
= plain
时, code_challenge
的值就是 code_verifier
当 code_challenge_method
= S256
时, code_challenge
就是将 code_verifier
先转 Ascii 码后,再进行 sha256 哈希,最后再进行 base64 编码。
示例
参考上方流程图,在使用 PKCE 时,需要对原先的授权码协议进行轻微改造,在请求 code
时,在客户端生成 code_verifier
并根据 code_challenge_method
生成 code_challenge
参数,然后将 code_challenge_method
和 code_challenge
两个参数传过去,此时的请求授权码链接应该是如下所示:
https://xxx.com?scope=openid%20profile%20email%20phone%20address&response_type=code&redirect_uri=xxx&state=xxx&client_id=xxxx&code_challenge=xxxx&code_challenge_method=plain
授权服务器会将 code
以及 code_challenge_method
和 code_challenge
进行保存,并返回给客户端 code
,客户端通过 code
获取 token
时,将 code_verifier
传递到令牌端,链接如下:
https://xxx.com/token?grant_type=authorization_code&code=xxx&client_id=xxxx&redirect_uri=xxx&code_verifier=xxx
令牌端对通过 code_challenge_method
对 code_verifier
编码生成新的 code_challenge
,然后于旧的 code_challenge
进行对比,验证通过即颁发 token
, 否则抛出异常。
结论
当授权码模式中不使用 app_secret
时,可以使用 PKCE
协议流程对 code
进行保护,防止 code
被窃取。
注意, APP 端每次通过 code
换 token
前,都要重新生成 code_verifier
,code_verifier
不允许重复利用。
参考资料