该死的开放API之新浪微博
新浪微博很火,开放平台很火,开发者很“火”。
开发者火是因为新浪微博开放平台对开发者很不友好,其 API 从实现到文档都很粗糙。
API 实现不标准还能忍,但是文档不说明清楚,要开发者自己摸索就离谱了。1看来新浪尚未有暇顾及第三方开发者,开放平台现在也只是“开门放出来”而已。结果就是开发者在很多毫无意义的事情上折腾,浪费时间!
我写这一系列文章的目的就是避免后来者遭同样的罪。
这是第一篇,关于 upload API 的 OAuth 验证失败问题。去论坛搜索一下 upload 就知道有多少人深受其害了。2
Upload API 的 OAuth 之所以难搞,部分是因为其 HTTP 请求格式的特殊性,主要是因为新浪微博那匪夷所思的实现。
Upload API 的特殊性在于其请求“采用 multipart/form-data 编码方式提交”3。根据 OAuth 1.0 协议,Content-Type 为 multipart/form-data 的 HTTP 请求,其 entity body 不参与 OAuth 签名。照此,upload API 的 OAuth 应该比其他普通 API 更简单,因为只有 OAuth 参数(oauth_ 开头的一系列特别参数)参与签名。所以,标准的 Signature Base String 应该是:POSThttp%3A%2F%2Fapi.t.sina.com.cn%2Fstatuses%2Fupload.jsonoauth_consumer_key%3Dxxxxxxxxxx%26oauth_nonce%3D15492994958798014939%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1312912324%26oauth_token%3Dyyyyyyyyyy%26oauth_version%3D1.0
与其对应的标准的 Authorization header 应该是:OAuth oauth_consumer_key=xxxxxxxxxx, oauth_token=yyyyyyyyyy, oauth_signature_method=HMAC-SHA1, oauth_version=1.0, oauth_nonce=15492994958798014939, oauth_timestamp=1312912324, oauth_signature=2RxNudXqdeSLXSxxRSgpIWUa3HI%3D”
然而,迎接你的将是:40107:Oauth Error: signature_invalid!
这是因为新浪微博 upload API 的实现要求 Signature Base String 包含除 pic 以外的参数。所以新浪微博需要的 Signature Base String 是这样的:POSThttp%3A%2F%2Fapi.t.sina.com.cn%2Fstatuses%2Fupload.json
lat%3D37.78711200
%26
long%3D-122.40846000
%26oauth_consumer_key%3Dxxxxxxxxxx%26oauth_nonce%3D18218585476538551879%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1312912886%26oauth_token%3Dyyyyyyyyyy%26oauth_version%3D1.0%26
status%3DPic
问题来了:status、lat、long 这些参数是在 entity body 中提交的,如前所述,它们不参与 OAuth 签名。为了遵循 OAuth 协议,必须将这些参数加入 URL query string 或 Authorization header。
规范的开发者这么做了,结果“错”了,新浪微博 server 还是返回:40107:Oauth Error: signature_invalid!
事实上,无论你怎么折腾,只要你遵循 OAuth 协议,你一定折腾不出来,因为新浪微博是不遵循 OAuth 协议的。
对于 Content-Type 为 multipart/form-data 的 HTTP 请求,新浪微博要求非 binary 参数(如 upload API 中的 pic 参数)参与 OAuth 签名,同时还要求这些参数是“无由来”的。也就是说,Signature Base String 要包含这些参数,但这些参数不能出现在 URL query string 或 Authorization header。这意味着新浪微博 server 端接收到请求后从 entity body 中提取了这些参数来进行 OAuth 签名验证。再强调一遍,对于 Content-Type 为 multipart/form-data 的 HTTP 请求,这是违反 OAuth 协议的。但在新浪微博上,我们就要是要违反 OAuth 协议才能通过 OAuth 验证。
即便不考虑像我这样耗时耗力的摸索过程,新浪微博的这种非标准实现也给开发者带来了很大麻烦,因为这导致了很多遵循 OAuth 协议实现的 OAuth 库无法直接使用。
在折腾的过程中,我还发现了另一个要命的 bug。如果 pic 的 Content-Disposition header 缺少 filename 参数的话,新浪微博也会返回“40107:Oauth Error: signature_invalid!
”,虽然 pic 跟 OAuth 毛关系都没有,虽然根据标准 filename 不是必须的,虽然事实上 filename 在这里是没用的。
最后总结一下搞定新浪微博 upload API 的“正确”方法:
- 所有参数用 multipart/form-data 格式提交,不能出现在 URL query string 或 Authorization header。
- 除 pic 以外的所有参数进入 Signature Base String 参与 OAuth 签名。
- pic 的 Content-Disposition header 必须包含 filename 参数。
- 官方论坛没什么帮助,哭的多,救的少。 ↩
- 由于没做 UTF-8 编码或 URL 编码造成的 OAuth 验证失败是开发者自己的错误。 ↩
- 本文也同样适用于其他采用 multipart/form-data 编码方式提交 HTTP 请求的 API,如 update_profile_image。