DIY OAuth 2 library:从97KB到22行Python

最近的项目需要用代码去读取一些Google Spreadsheet的数据,看了下Google官方的Spreadsheet API,读取数据这些好说,就是xml格式我比较讨厌。于是想用alt=json这个json输出API,试用发现,对于ClientLogin(浏览器cookie)直接调用Spreadsheet API的Google Apps用户而言,这个json输出是400不可用的。于是只能撸一个OAuth读取数据试试了。

看了下官方的Google Data APIs Python Client Library,我勒个擦啊,一大坨垃圾依赖,比如OpenSSL.cryptohttplib2等。

仔细看了下Google OAuth 2.0的文档,似乎也不复杂的样子,至少比OAuth 1.0 那种3 legged login 简单多了。于是就自己DIY了一个

OAuth 2.0把流程分为了几个scenario,比如用于Web交互,客户端app,设备,等等。我需要的只是服务器读取某个Google Spreadsheet的一些数据,无须用户点击几个“确认”,所以属于Service Account

当然,官方强烈不推荐你自己去实现OAuth 2.0这一套流程

> The rest of this article describes the specifics of creating a JWT, signing the JWT, forming the access token request, and handling the response. Libraries should abstract these specifics from your application. Again, developers are strongly encouraged to attempt to use an existing library rather than building your own support for server-to-server interactions.

所以我就直接去逆向他们的Tasks API with a Service account sample

安装了一大票依赖,痛苦编译好pyopenssl之后,直接运行,出错:

  oauth2client.client.AccessTokenRefreshError: invalid_grant

加个Fiddler代理调试:

  http = httplib2.Http(
    proxy_info=httplib2.ProxyInfo(
      httplib2.socks.PROXY_TYPE_HTTP, 
      proxy_host='10.0.18.2', 
      proxy_port=8888
  ), ca_certs='FiddlerRoot.cer')

发现https不支持。于是

  httplib2.debuglevel = 4

终于看到一坨东西了。

对比上面那个 OAuth 2文档,可以轻松写出OAuth 2.0获得access_token的代码:

需要用到API用户email,p12密钥两个东西,可以从Google API Console里获得。

  import base64

  b64 = lambda x:base64.urlsafe_b64encode(x).rstrip('=')

  header = '{"alg":"RS256","typ":"JWT"}'
  ts_now = int(time.time())

  claim_set = {
      "iss": email,
      "scope": ' '.join(scope),
      "aud": "https://accounts.google.com/o/oauth2/token",
      "exp": ts_now+3600,
      "iat": ts_now,
  }

  jwt = '%s.%s' % (url_b64(header), url_b64(json.dumps(claim_set)))
  # convert pkcs12 to private key
  p = Popen('openssl pkcs12 -in key.p12 -nocerts -passin stdin -nodes -out key.pem', shell=True, stdin=PIPE, stdout=PIPE)
  p.stdin.write('notasecret') # Google's key default password
  p.stdin.close()
  p.wait()
  # RSA sign with sha256 using private key
  p = Popen('openssl dgst -sha256 -sign 'key.pem' -keyform PEM', shell=True, stdin=PIPE, stdout=PIPE)
  p.stdin.write(jwt)
  p.stdin.close()
  sig = p.stdout.read()
  p.wait()

  print '%s.%s' % (jwt, url_b64(sig))

有了access_token之后,获取spreadsheet数据就很简单了,我也拿sample里的spreadsheet作为例子:

https://spreadsheets.google.com/feeds/list/o13394135408524254648.240766968415752635/od6/public/values?alt=json&access_token=1/XXXXX

其中1/XXXXX 就是OAuth 2.0得到的access_token。

对比一下,上面提到的Google官方的OAuth 2 library是97KB大小,自己DIY的算上注释都才只有22行。。。

Google客户端的代码是用了import OpenSSL来直接实现RSA SHA256签名,我直接先后启动两个openssl命令行实现的。好处是不用麻烦的编译pyopenssl这个库依赖了。服务器部署方便。

Comments