本文共 10362 字,大约阅读时间需要 34 分钟。
让我先吐槽一下新浪微博的那个 OAuth 文档,写得就像个锤子一样!
OAuth 是一套认证形式,并被逐渐推荐为一套标准,它的老家在 。
OAuth 实现的是一套三方委托认证的模式。
举例来说,有人想知道你新浪微博上的所有粉丝都有哪些,而你也愿意让他知道。但是,这里有一个问题,如果你直接把账号和密码告诉这个人的话,你可能会觉得很不安全,你只想把有限的东西给这个人,而不是把账号和密码都交出来。
于是,可以使用这样的一种方式,这个人去找新浪要你的粉丝资料,新浪就问你,你愿意把你的资料给这个人么?你说愿意。好,新浪就把你的资料给这个人了,只是给这个人。于是,这个人在你的授权下得到了你的粉丝资料,但是你并没有把账号和密码泄漏出去。
整个过程大概如下图所示:
4.Here you are --------------------- | | | | ------------- | 2.argee? | | 1.request | --------- | SINA | <--------- | | | | | | | ------------- | | v ^ | v------------- | -------------| | | | || YQ |---------- | ZYS || | 3.Yes | |------------- -------------
记住这里的 4 个过程:
requestargee?YesHere you are
从图中可以看出,整个过程中 SINA 都扮演着一个中间者的作用, ZYS 和 YQ 通过与 SINA 的参数传递实现授权访问。
上面的前 3 个过程对应着 3 个新浪的认证 URL (最后一个是具体的应用 API 了):
这 3 个 URL 的作用是这样的:
下面开始实战。
首先,需要去新浪微博的开放平台 上注册用户,然后创建一个应用。在应用的页面,你可以找到一对 APP_KEY 和 APP_SECRET 。
现在我们开始第一步,也是最难的一步。
OAuth 的认证过程无非就是通过 HTTP 协议把几个参数传递一下,没有什么复杂的东西。但是,因为文档描述问题,这些参数应该怎么包装,签名应该如何做,如果不得要领是很烦人的事。我们在这一步把参数的包装和签名计算方法搞定,之后的两步就水道渠成了。
OAuth 的认证参数通常是放到头部信息中传递的(当然,也可以放到 URL 中),这个头部信息如何包装我们之后会细细介绍。
参看官方文档 ,得知请求 request_token 时需要的参数有:
上面的参数当中,除了最后一个 oauth_signature ,其它的都应该很清楚
oauth_signature 是通过 HMAC-SHA1 算法,处理一个叫 Base String 的字符串,最后取 base64 编码得到的。而 Base String 字符串是由访问方法,访问 URL ,及前面的 5 个参数拼接而成。
假设访问方法是 GET ,要访问的 URL 是 ,同时假设这 5 个参数为:
最后的 Base String 即是使用 & 连接这三组数据, url 需要进行 URL 编码( / 也要编码,所以要加一个 safe='' )。我们重点说 p 如何得到。
p 由上面的 5 个参数 按序 拼接而成。
from urllib import urlencode, quoteparams = [ ('oauth_consumer_key', '1234567'), ('oauth_version', '1.0'), ('oauth_timestamp', '1307980836'), ('oauth_nonce', 'xxoo'), ('oauth_signature_method', 'HMAC-SHA1'),]params.sort()p = quote(urlencode(params))
简单说,就是排序后进行 URL 编码再进行第二次 URL 编码。于是:
from urllib import urlencode, quotemethod = 'GET'url = 'http://api.t.sina.com.cn/oauth/request_token'params = [ ('oauth_consumer_key', '1234567'), ('oauth_version', '1.0'), ('oauth_timestamp', '1307980836'), ('oauth_nonce', 'xxoo'), ('oauth_signature_method', 'HMAC-SHA1'),]params.sort()p = quote(urlencode(params))base_string = '%s&%s&%s' % (method, quote(url, safe=''), p)-------------GET&http%3A%2F%2Fapi.t.sina.com.cn%2Foauth%2Frequest_token&oauth_consumer_key%3D1234567%26oauth_nonce%3Dxxoo%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1307980836%26oauth_version%3D1.0
得到 Base String 后,需要使用 HMAC-SHA1 算法对其进行处理(HMAC 算法需要一个 KEY ,在实际认证中使用 APP_SECRET 或者 *_secret ),在 Python 中有现成的模块可以使用:
import hmac, hashlibKEY = 'abc'h = hmac.new(KEY, base_string, hashlib.sha1)s = h.digest()signature = quote(s.encode('base64').rstrip())#gA6Eo8xQgEgHMbaOdIdXFwbH/1Y%3D
最后的结果,就是我们需要的签名值。
#! /usr/bin/python# -*- coding: utf-8 -*-from urllib import quote, urlencodeimport urllib2import timeimport uuidimport hmac, hashlibdef get_token(): URL = 'http://api.t.sina.com.cn/oauth/request_token' APP_KEY = '' #写自己的 APP_SECRET = '' params = [ ('oauth_consumer_key', APP_KEY), ('oauth_nonce', uuid.uuid4().hex), ('oauth_signature_method', 'HMAC-SHA1'), ('oauth_timestamp', int(time.time())), ('oauth_version', '1.0'), ] params.sort() p = 'GET&%s&%s' % (quote(URL, safe=''), quote(urlencode(params))) signature = hmac.new(APP_SECRET + '&', p, hashlib.sha1).digest().encode('base64').rstrip() params.append(('oauth_signature', quote(signature))) h = ', '.join(['%s="%s"' % (k, v) for (k, v) in params]) r = urllib2.Request(URL, headers={ 'Authorization': 'OAuth realm="", %s' % h}) data = urllib2.urlopen(r).read() token, secret = [pair.split('=')[1] for pair in data.split('&')] return token, secretif __name__ == '__main__': print get_token()
这一步过后,我们就获得了 request_token 和 request_secret 。
这一步做得事情就简单多了,只需要把上一步获取到的 request_token 作为 oauth_token 这个参数添加到 URL 后面进行访问就可以了。
新浪微博的系统会自动作用户的登陆处理(或者可以传入 userId 和 passwd 直接获取到verifier ),然后用户可以选择是否授权给你的应用。确认后,你可以得到 verifier 用于下一步。
参见官方的文档 ,除了oauth_token 这个必须的参数以个,还有几个可选的参数:
其中 userId 和 passwd 是和 oauth_callback=json|xml 配合使用的。它直接返回verifier ,没有确认授权页面。
假设上一步我们得到的 request_token 是 808cb0645284a4e0995fbcc7ac29e381 ,那么最简单的一个 URL 就是
http://api.t.sina.com.cn/oauth/authorize?oauth_token=808cb0645284a4e0995fbcc7ac29e381
在浏览器当中直接访问这个地址即可。新浪微博会让你登陆并确认授权。之后会在页面中显示verifier 。
如果是 web 应用的话,一般我们会使用回调地址的方式,使用 oauth_callback 传递地址:
http://api.t.sina.com.cn/oauth/authorize?oauth_token=808cb0645284a4e0995fbcc7ac29e381&oauth_callback=http%3A%2F%2Flocalhost%3A8080%2F
上面的 URL 中,我们把 进行 URL 编码后放到 oauth_callback参数当中。
访问并授权后,页面会重定向到:
http://localhost:8080/?oauth_token=d18fbed6c40ba7b961aa860087b427e7&oauth_verifier=706286
这里有 oauth_token 和 oauth_verifier ,你可以在后端处理它们了。
经过这一步,我们现在手头上有 request_token , request_secret , verifier 这三个东西了。下一步会用到它们。
这一步我们使用手中的 request_token , request_secret , verifier 去换取一对access_token 和 access_secret 。整体上和第一步的操作相同。就是计算签名值和包装参数。前面是使用的 GET 方法,这里要使用 POST 方法了。
参见官方的文档 ,需要传递的 OAuth 参数有:
这里计算 oauth_signature 时不同的地方就是要使用 request_secret 作为 HMAC 的 KEY 的一部分:
前面的是这样:
signature = hmac.new(APP_SECRET + '&', p, hashlib.sha1).digest().encode('base64').rstrip()
现在需要这样:
signature = hmac.new(APP_SECRET + '&' + request_token, p, hashlib.sha1).digest().encode('base64').rstrip()
完整的代码如下:
#! /usr/bin/python# -*- coding: utf-8 -*-from urllib import quote, urlencodeimport urllib2import timeimport uuidimport hmac, hashlibdef get_access_token(token, secret, verifier): URI = 'http://api.t.sina.com.cn/oauth/access_token' APP_KEY = '' #写自己的 APP_SECRET = '' headers = [ ('oauth_consumer_key', APP_KEY), ('oauth_nonce', uuid.uuid4().hex), ('oauth_signature_method', 'HMAC-SHA1'), ('oauth_timestamp', int(time.time())), ('oauth_version', '1.0'), ('oauth_token', token), ('oauth_verifier', verifier), ('oauth_token_secret', secret), ] headers.sort() p = 'POST&%s&%s' % (quote(URI, safe=''), quote(urlencode(headers))) signature = hmac.new(APP_SECRET + '&' + secret, p, hashlib.sha1).digest().encode('base64').rstrip() headers.append(('oauth_signature', quote(signature))) h = ', '.join(['%s="%s"' % (k, v) for (k, v) in headers]) r = urllib2.Request(URI, headers={ 'Authorization': 'OAuth realm="", %s' % h}) data = urllib2.urlopen(r, data='').read() token, secret, user_id = [pair.split('=')[1] for pair in data.split('&')] return token, secret, user_idif __name__ == '__main__': print get_access_token('0a426d1b8695df7888c0b79d77c2071c', '77764447cbdd856463b3198c935bad41', '601738')
上面代码中,调用 get_access_token 函数的三个参数都在上一步获取到了的。
访问之后,你可以得到一个新的 access_token ( request_secret 没有变的,当然,之后我们可以把它改叫做 access_secret ),之后我们就使用它去访问各个需要授权的 API 接口了。
现在我们手头上有 access_token 和 access_secret 了,作为示例,我们去获取一下授权者的个人信息。
查看官方文档 ,这个验证用户身份的 API 可以获取用户信息。
像之前的认证过程一样,需要在头部中带上 OAuth 的那 7 个参数:
应该说的前面已经说过了,直接上代码吧:
#! /usr/bin/python# -*- coding: utf-8 -*-from urllib import quote, urlencodeimport urllib2import timeimport uuidimport hmac, hashlibdef get_info(token, secret): URI = 'http://api.t.sina.com.cn/account/verify_credentials.json' APP_KEY = '' #写自己的 APP_SECRET = '' headers = [ ('oauth_consumer_key', APP_KEY), ('oauth_nonce', uuid.uuid4().hex), ('oauth_signature_method', 'HMAC-SHA1'), ('oauth_timestamp', int(time.time())), ('oauth_version', '1.0'), ('oauth_token', token) ] headers.sort() p = 'POST&%s&%s' % (quote(URI, safe=''), quote(urlencode(headers))) signature = hmac.new(APP_SECRET + '&' + secret, p, hashlib.sha1).digest().encode('base64').rstrip() headers.append(('oauth_signature', quote(signature))) h = ', '.join(['%s="%s"' % (k, v) for (k, v) in headers]) r = urllib2.Request(URI, data='', headers={ 'Authorization': 'OAuth realm="", %s' % h}) data = urllib2.urlopen(r).read() return dataif __name__ == '__main__': print get_info('97de3e2422c2b5fe4328d6cdc1ac5597', '2563a0ac36baecbbb7d93a4987d899df')
代码中调用 get_info 函数需要的两个参数就是前面我们已经取得的 access_token 和access_secret 。
转载地址:http://cnsno.baihongyu.com/