#oauth


PKCE全称是Proof Key for Code Exchange,在2015年发布,它是OAuth 2.0核心的一个扩展协议,所以可以和现有的授权模式结合使用,比如Authorization Code+PKCE, 这也是最佳实践,PKCE最初是为移动设备应用和本地应用创建的, 主要是为了减少公共客户端的授权码拦截攻击。 在最新的OAuth 2.1规范中,推荐所有客户端都使用PKCE,而不仅仅是公共客户端,并且移除了Implicit隐式和Password模式,那之前使用这两种模式的客户端怎么办? 是的,现在都可以尝试使用Authorization Code+PKCE的授权模式。那PKCE为什么有这种魔力呢? 实际上它的原理是客户端提供一个自创建的证明给授权服务器,授权服务器通过它来验证客户端,把访问令牌(access_token)颁发给真实的客户端而不是伪造的。

客户端类型

上面说到了PKCE主要是为了减少公共客户端的授权码拦截攻击,那就有必要介绍下两种客户端类型了。

OAuth 2.0核心规范定义了两种客户端类型, confidential 机密的, 和 public 公开的, 区分这两种类型的方法是, 判断这个客户端是否有能力维护自己的机密性凭据 client_secret

  • confidential
    对于一个普通的web站点来说,虽然用户可以访问到前端页面,但是数据都来自服务器的后端api服务,前端只是获取授权码code,通过code换取access_token这一步是在后端的api完成的,由于是内部的服务器,客户端有能力维护密码或者密钥信息,这种是机密的的客户端。
  • public
    客户端本身没有能力保存密钥信息,比如桌面软件,手机App,单页面程序(SPA),因为这些应用是发布出去的,实际上也就没有安全可言,恶意攻击者可以通过反编译等手段查看到客户端的密钥,这种是公开的客户端。

OAuth 2.0授权码模式(Authorization Code)中,客户端通过授权码code向授权服务器获取访问令牌(access_token)时,同时还需要在请求中携带客户端密钥(client_secret),授权服务器对其进行验证,保证access_token颁发给了合法的客户端,对于公开的客户端来说,本身就有密钥泄露的风险,所以就不能使用常规OAuth 2.0的授权码模式,于是就针对这种不能使用client_secret的场景,衍生出了Implicit隐式模式,这种模式从一开始就是不安全的。在经过一段时间之后,PKCE扩展协议推出,就是为了解决公开客户端的授权安全问题。

授权码拦截攻击

oauth2.0.svg

上面是OAuth 2.0授权码模式的完整流程,授权码拦截攻击就是图中的C步骤发生的,也就是授权服务器返回给客户端授权码的时候,这么多步骤中为什么C步骤是不安全的呢?在OAuth 2.0核心规范中,要求授权服务器的anthorize endpointtoken endpoint必须使用TLS(安全传输层协议)保护,但是授权服务器携带授权码code返回到客户端的回调地址时,有可能不受TLS的保护,恶意程序就可以在这个过程中拦截授权码code,拿到code之后,接下来就是通过code向授权服务器换取访问令牌access_token,对于机密的客户端来说,请求access_token时需要携带客户端的密钥client_secret,而密钥保存在后端服务器上,所以恶意程序通过拦截拿到授权码code也没有用,而对于公开的客户端(手机App,桌面应用)来说,本身没有能力保护client_secret,因为可以通过反编译等手段,拿到客户端client_secret,也就可以通过授权码code换取access_token,到这一步,恶意应用就可以拿着token请求资源服务器了。

state参数,在OAuth 2.0核心协议中,通过code换取token步骤中,推荐使用state参数,把请求和响应关联起来,可以防止跨站点请求伪造-CSRF攻击,但是state并不能防止上面的授权码拦截攻击,因为请求和响应并没有被伪造,而是响应的授权码被恶意程序拦截。

PKCE 协议流程

oauth2.1pkce.svg

PKCE协议本身是对OAuth 2.0的扩展,它和之前的授权码流程大体上是一致的。区别在于,在向授权服务器的authorize endpoint请求时,需要额外的code_challengecode_challenge_method参数,向token endpoint请求时,需要额外的code_verifier参数,最后授权服务器会对这三个参数进行对比验证,通过后颁发令牌。

原理分析

上面我们说了授权码拦截攻击,它是指在整个授权流程中,只需要拦截到从授权服务器回调给客户端的授权码code,就可以去授权服务器申请令牌了,因为客户端是公开的,就算有密钥client_secret也是形同虚设,恶意程序拿到访问令牌后,就可以光明正大的请求资源服务器了。

PKCE是怎么做的呢?既然固定的client_secret是不安全的,那就每次请求生成一个随机的密钥(code_verifier),第一次请求到授权服务器的authorize endpoint时,携带code_challengecode_challenge_method,也就是code_verifier转换后的值和转换方法,然后授权服务器需要把这两个参数缓存起来,第二次请求到token endpoint时,携带生成的随机密钥的原始值(code_verifier),然后授权服务器使用下面的方法进行验证:

  • plain
    code_challenge = code_verifier
  • sha256
    code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

通过后才颁发令牌,那向授权服务器authorize endpointtoken endpoint发起的这两次请求,该如何关联起来呢?通过授权码code即可,所以就算恶意程序拦截到了授权码code,但是没有code_verifier,也是不能获取访问令牌的,当然PKCE也可以用在机密(confidential)的客户端,那就是client_secret+code_verifier双重密钥了。

参考连接