PKCE(读作pixy)是RFC: 7636 Proof Key for Code Exchange by OAuth Public Clients对OAuth2.0的Authorization Code Flow所做的扩展。

Authorization Code Flow需要客户端持有client_id和client_secret。client_id在第一步用来换取authz code,client_secret在第二步用来换取access token。如果是桌面或者移动app这些非云端应用的话,client_id和client_secret都要打包到app中。因为app的内容是运行在客户端的电脑上的,所以打包的client_id和client_secret容易泄露。rfc7636把这些app成为public client,假定这些app不能保守client_id和client_secret,并为破除这个弊端,打造了Proof Key for Code Exchange这个流程来至少避免把client_secret打包到app中。

rfc7636的思想很简单,就是让public client在运行时候使用一次性生成的随机码来向服务器换取code,服务器需要记住用于交互用的随机码,在签发access token的时候需要检查客户端提交的随机码(或者生成随机码的生成码),如果不对则拒绝签发access token。

具体操作过程如下:

  • 客户端生成一个code_verifier,作为生成码
    • 生成码必须是强随机的,不能被猜到。长度从43到128之间,可以包括在Section 2.3 of [RFC3986]中定义的字符
  • 客户端可以直接把生成码发给服务端,或者把调理后的生成码发给服务端
    • 生成码在request_uri的code_challenge 参数中传递
    • 如果不调理,那么需要在request_uri指定code_challenge_method参数为plain
    • 如果调理,目前只有sha256一种调理方法。如果使用的话,需要在request_uri指定code_challenge_method参数为S256,并通过BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))来生成code_challenge
  • 服务端返回authz code
  • 客户端发送authz code和code_verifier到服务端令牌端点
  • 服务端验证authz code和code_verifier,然后返回access token

参考

2020-05-1更新

使用Python来模拟生成code_verifier和code_challenge的算法:

>>> import string, base64
>>> from hashlib import sha256
>>> code_verifier = ''.join(random.choice(string.ascii_lowercase) for i in range(32))
>>> code_verifier
'auriavjuviqiojozncangfshwfzvsvyv'
>>> code_challenge = base64.urlsafe_b64encode(sha256(code_verifier.encode()).digest())..rstrip(b'=')
>>> code_challenge
'gb1c_gwBtQqi5EXpMGi5XaJVfkeXQJGrba0PS-93Qcg'
>>> len(code_challenge)
43
>>> base64.urlsafe_b64encode(sha256(b'auriavjuviqiojozncangfshwfzvsvyv').digest()).rstrip(b'=')
b'gb1c_gwBtQqi5EXpMGi5XaJVfkeXQJGrba0PS-93Qcg'

(更新完)