新浪微博OAuth 认证详解

薄瑞
2023-12-01
众所周知,新浪微博开放平台采用的是OAuth(1.0) 认证方式。那么,
OAuth是什么呢?
说白了OAuth其实就是个协议,也可以说是一个标准,一个Pattern,一个模型。这个协议是用来解决什么问题的呢?举个例子,你在建设银行开了个账户,假设现在你想在申银万国(券商)那开个股票账户炒股,那么申银万国如何访问你的建行账户,从里面转钱出来买股票呢? 很显然,你不想告诉它你银行账户的密码让它直接去银行获取你的账户信息。那么解决这个问题的方法就是:授权,你去建行那里授权申银万国,申银万国获得你的授权后才能获取你的银行账户信息。当然这里面还有一个隐含的前提,就是申银万国必须建设银行认可的合法的合作机构。意思是说系统不会允许你授权给任意一个未知的所谓券商去建行获取你的账户信息。
同样回到新浪微博这个case,新浪微博是服务提供者,微博用户是它的服务对象。现在假设微博用户想通过一个第三方应用(app)来访问其存在微博服务端的数据而不想将密码告知给这个app,解决这个问题,同样采用的也是用户授权的方式。同样前提也是这个app也必须是在新浪微博注册过的。

而定义app如何通过微博服务器认证这一过程就是OAuth协议的主要内容。
这一过程中可以抽象出三个角色:Server(Service Provider),Client(Consumer), User(Resource Owner)
在新浪微博开放平台中,这三个的角色(实例化后)是: 微博服务端,微博 app,微博用户
OAuth是如何定义认证过程的?
OAuth定义的认证过程分三步:
1.    Client从server获取RequestToken
2.    User授权Client获取的RequestToken,client获取authorization code(授权码)
3.    Client以RequestToken和authorization code从server获取AccessToken
在此过程中RequestToken是一个临时性的东西。完成这三步后,RequestToken就失去价值了。另外RequestToken在一段时间内没有使用,也会过期从而失效。而AccessToken是一个持久性的东西,它是user对client的授权凭证,会一直有效,直到user取消授权。
认证过程中第一步和第三步发送的请求是需要签名的。

如何实现OAuth签名?
首先来认识一下OAuth协议参数(protocol parameter):
oauth_consumer_key, 就是你的appKey
oauth_signature_method, 目前使用的值是"HMAC-SHA1"
oauth_timestamp, 自1970/1/1 00:00:00到现在时间(UTC)的秒数
oauth_nonce, 一个无意义的量,每次请求随机生成, 比如GUID
oauth_version, OAuth协议版本。目前使用的值是“1.0”
oauth_token, 在认证期间(第二、三步)其值是RequestToken,在登陆之后是AccessToken
oauth_verifier, 认证第二步server返回的授权码,该参数仅在登陆期间使用。
oauth_callback, 认证第一步client传给server的回调地址, user授权成功后会转到此地址, 该参数仅在认证期间使用。
oauth_signature,对其他OAuth协议参数以及HTTP请求参数按一定规则拼凑而成的signatureBaseString进行签名后得到的值。

另: 还有一个OAuth参数叫做realm, 这个参数适用于user在server端存储的数据分布在不同的地方,而这些地方使用不同的认证方式。目前,这个参数不适用于新浪微博,在此文中不做表述。

注:本文中提到的参数指的是,包含参数名和参数值的键值对。签名就是基于OAuth协议参数以及HTTP 请求 相关 参数的而做的。
签名的具体计算规则如下:
oauth_signature = HMAC-SHA1(signatureBaseString, secret)
注: secret是HMAC-SHA1算法的密钥(key)
secret取值规则如下:
在认证第一步获取RequestToken时, 是:  appSecret+&  (没有+号)
在认证第三步获取RequestToken时, 是:  appSecret+&+requestTokenSecret  (没有+号)
在认证之后访问其他需要认证的API时, 是:  appSecret+&+accessTokenSecret (没有+号)
signatureBaseString计算规则如下:
signatureBaseString = HTTP_Method + & + RFC3986Encode(Base_String_Uri) + & + RFC3986Encode(Sorted_Params) (没有+号和空格)

HTTP_Method就是GET,POST,DELETE等,注意要大写。
Base_String_Uri就是请求访问的Url, 注意不包含querystring,也就是‘?’之前的部分,全部小写.
RFC3986Encode就是大写的URLEncode. 如 ’:’ Encode之后是’ %3A’而不是’%3a’。
Sorted_Params计算规则如下:
1.    将所有请求参数(包括除oauth_signature外的OAuth协议参数以及HTTP请求相关参数)的参数名和值进行RFC3986Encode然后以参数名按字母顺序(A-Z)。
2.    对每个参数,用’=’连接参数名与参数值。
3.    以排序后顺序,再以’&’连接所有上一步形成的字符串。(注意,最后形成的字符串后没有一个多余的’&’)

Sorted_Params = 
{
var sortedParamString;
params.Sort(); // 注意: params中不包含oauth_signature参数
foreach(var param in params)
sortedParamString+=RFC3986Encode(param.Name)+”=”+RFC3986Encode(param.Value)+”&”;
sortedParamString  = sortedParamString.TrimEnd(‘&’);
return sortedParamString;
}
知道了如何签名,那么
如何在HTTP request中传递OAuth签名数据?
传递的方式根据request 方法不同可不同:
    如果是HTTP GET(DELETE) 方式,可以在HTTP header中传递也可以在query string中传递。
    如果是HTTP POST方式,既可以在HTTP header中传递也可以request body中传递。
注意:此处OAuth签名数据指的是,OAuth协议参数包括签名(即所有以Oauth_开头的参数)。且一个请求中, 它们只能使用一种传递方式。
总结起来有如下三种方式:
1.    在HTTP header中传递:
这种方式其实就是在HTTP header中添加一个名为Authorization的header。AuthorizationHeader值计算规则为:
AuthorizationHeader = auth-scheme + 空格 + 所有排序后参数名和值连接起来的字符串
auth-scheme值为: OAuth
下面是一个AuthorizationHeader值的例子: (其中没有换行符,值用双引号””包装)
OAuth oauth_consumer_key="0685bd9184jfhq22",
oauth_token="ad180jjd733klru7",oauth_signature_method="HMAC-SHA1",
oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",oauth_timestamp="137131200",oauth_nonce="4572616e48616d6d65724c61686176",oauth_version="1.0"

注意:计算规则与签名计算规则类似(但有所不同),参数名和值都需要RFC3986Encode。
另: 不要试图验证本文中示例数据的正确性。

2.    在HTTP query string中传递
这种方式相对简单就是按照query string的模式把参数一个一个连起来。当然参数名和值也是要做RFC3986Encode的。
下面是一个例子: (其中没有换行符)
?oauth_consumer_key=0685bd9184jfhq22&oauth_token=ad180jjd733klru7&oauth_signature_method=HMAC-SHA1&oauth_signature=wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D&oauth_timestamp=137131200&oauth_nonce=4572616e48616d6d65724c61686176&oauth_version=1.0

注意:Query String中可能还有其他请求相关参数。

3.    在HTTP body 中传递 (需是POST方式)
要想在HTTP body中传递OAuth签名数据,必需满足一定条件:
    此HTTP request必需是singlepart而非multipart。(注: 发送带图片的微博采用的是multipart方式。)
    此HTTP request采用"application/x-www-form-urlencoded"作为其Content-Type。
下面是一个例子: (其中没有换行符)
oauth_consumer_key=0685bd9184jfhq22&oauth_token=ad180jjd733klru7&oauth_signature_method=HMAC-SHA1&oauth_signature=wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D&oauth_timestamp=137131200&oauth_nonce=4572616e48616d6d65724c61686176&oauth_version=1.0
注:字符串连接规则和query string方式一样,参数名和值要做RFC3986Encode。

知道了OAuth认证过程以及如何做签名以及如何在HTTP request中传递签名数据,那么
如何在新浪开放平台中实现OAuth认证?

现在以一个示例演示一个desktop(mobile) app如何在新浪微博开放平台中实现OAuth三步认证以登陆微博。
前提:已在微博开放平台创建app(获取appKey和appSecret)。
1.    app发送HTTP GET请求到 http://api.t.sina.com.cn/oauth/request_token 获取request_token
此时,app所拥有的信息只有:appKey和appSecret。appKey是app的唯一标识。要想去新浪微博服务器请求token,必须告诉它是谁在请求,所以这个参数必须传递给服务器。appSecret是微博开放平台为每个app分配的用以进行机密数据交换的密钥。(这是一个保密项,不要公开否则别人可能会冒你的名去发送请求),appSecret主要用来进行数据签名。
这一步请求需传递的参数如下:
oauth_consumer_key, 就是你的appKey
oauth_signature_method, 目前使用的值是"HMAC-SHA1"
oauth_timestamp, 自1970/1/1 00:00:00到现在时间(UTC)的秒数
oauth_nonce, 一个无意义的量,每次请求随机生成,比如GUID
oauth_version, OAuth协议版本。目前使用的值是“1.0”。
oauth_callback, 回调地址,如果是desktop(mobile)app, 使用oob/xml/json;Web app使用你指定的地址
oauth_signature, 对以上参数进行签名后得到的值。

注:签名的密钥(key)使用appSecret。

Client发送的request authorization header如下:(使用HTTP GET, 以HTTP header方式传递OAuth参数)
Authorization: OAuth oauth_callback="oob", oauth_consumer_key="872044423", oauth_nonce="8833bad9-987b-4dea-b333-ad4dfae380e8", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1316324017", oauth_version="1.0", oauth_signature="5kbVhDuecTELVAhw8w%2Bz%2Br6z9Jo%3D"
Server的返回如下:
oauth_token=b128f2f6a7e5c8e53999b54af2a926f6&oauth_token_secret=ea40e2bf66a98b8490ac31874f4b0edc
此时返回的oauth_token即为RequestToken, oauth_token_secret即为RequestTokenSecret。

2.    app发送HTTP GET请求到 http://api.t.sina.com.cn/oauth/authorize 获取授权码(oauth_verifier)
如果是desktop(mobile) app, 新浪微博提供了隐式的基于用户名密码的授权方式。
此时请求需传递的参数如下:
oauth_callback, 使用xml
oauth_token, 上一步server返回的RequestToken
userid, 欲授权的用户名
passwd,欲授权的用户密码
Client发送的request如下:(示例使用HTTP GET, 以HTTP query string方式传递OAuth参数)
http://api.t.sina.com.cn/oauth/authorize?oauth_callback=xml&oauth_token=b128f2f6a7e5c8e53999b54af2a926f6&passwd=amicroblogtest&userId=AMicroblogTest%40sina.com
Server的返回如下:
<?xml version="1.0" encoding="UTF-8"?><oauth><oauth_token>b128f2f6a7e5c8e53999b54af2a926f6</oauth_token><oauth_verifier>533348</oauth_verifier></oauth>

如果是web app,此时请求需传递的参数如下:
oauth_callback, 使用指定的回调地址, 如:  http://localhost/callback
oauth_token, 上一步server返回的RequestToken
Client发送的request如下:
http://api.t.sina.com.cn/oauth/authorize?oauth_callback=http%3A%2F%2Flocalhost%2Fcallback&oauth_token=b128f2f6a7e5c8e53999b54af2a926f6
此时浏览器定位到新浪微博授权页面,用户在此页面输入用户名密码并确认授权,成功后页面自动转向 http://localhost/callback?oauth_token=  b128f2f6a7e5c8e53999b54af2a926f6& oauth_verifier=533348
web app需在此页面page_load事件里接收授权码oauth_verifier, 并发起第三步请求。

注:desktop(mobile) app也可以像web app那样,通过调用浏览器转到新浪微博授权页面, 
http://api.t.sina.com.cn/oauth/authorize?oauth_callback=oob&oauth_token=b128f2f6a7e5c8e53999b54af2a926f6
不过此时,oauth_callback值为oob。用户授权完毕后,浏览器页面会显示授权码。app可以提示用户复制该授权码,到指定输入框以接收授权码从而进行第三步。

3.    app发送HTTP GET请求到 http://api.t.sina.com.cn/oauth/access_token 获取access_token

这一步请求需传递的参数如下:
oauth_consumer_key, 就是你的appKey
oauth_signature_method, 目前使用的值是"HMAC-SHA1"
oauth_timestamp, 自1970/1/1 00:00:00到现在时间(UTC)的秒数
oauth_nonce, 一个无意义的量,每次请求随机生成,比如GUID
oauth_version, OAuth协议版本。目前使用的值是“1.0”。
oauth_token, 第一步server返回的RequestToken
oauth_verifier, 第二步获取的授权码
oauth_signature, 对以上参数进行签名后得到的值。

注:签名的密钥(key)使用appSecret&requestTokenSecret。

Client发送的request authorization header如下:(示例使用HTTP GET, 以HTTP header方式传递OAuth参数)
Authorization: OAuth oauth_callback="oob", oauth_consumer_key="872044423", oauth_nonce="c678eaa7-f901-4de6-a43d-533901d58a65", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1316324689", oauth_token="b128f2f6a7e5c8e53999b54af2a926f6", oauth_verifier="533348", oauth_version="1.0", oauth_signature="9b26b2PGsa4Yfk9ZMD6UBZ5g4SQ%3D"

Server的返回如下:
oauth_token=db72f138f18df8db75f003f770fd77a0&oauth_token_secret=3b22eadd467dc4a255fb4e6ad7e4c1b6&user_id=2320057781
此时返回的oauth_token即为AccessToken, oauth_token_secret即为AccessTokenSecret。
AccessToken以及AccessTokenSecret获取后,可以储存起来以备后续请求使用。

如何通过AccessToken访问新浪开放平台API?
对于新浪开发平台API文档中说明需要登录的所有API都需要通过OAuth认证方式才能访问。若以及获得用户的AccessToken,即可通过如下方式发送请求访问。
此时需传递的参数因请求的API不同而不同,与API逻辑相关联的请求参数且称之为请求相关参数,区别于OAuth协议参数。
每次请求的OAuth协议参数不变,如下:
oauth_consumer_key, 就是你的appKey
oauth_signature_method, 目前使用的值是"HMAC-SHA1"
oauth_timestamp, 自1970/1/1 00:00:00到现在时间(UTC)的秒数
oauth_nonce, 一个无意义的量,每次请求随机生成,比如GUID
oauth_version, OAuth协议版本。目前使用的值是“1.0”。
oauth_token, 已获取的AccessToken
oauth_signature, 对以上参数以及请求相关参数进行签名后得到的值。

签名的方法与前文所述一样,所有参数(包括OAuth参数以及请求相关参数)进行RFC3986Encode,排序,联合HTTP Method和Base_String_URI连接形成signatureBaseString, 使用appSecret&accessTokenSecret进行HMAC-SHA1签名。然后以可用的传递方式传递数据即可。
 类似资料: