腾讯微博开放平台OAuth1.0授权完整流程(C#)

一、OAuth授权和版本、开放平台

首先要明确,在协议中,角色分为终端用户、第三方应用(Client)、服务提供者。

关于OAuth授权协议的起源和更多基本概念,我不能描述更多,google官网能更好行使这个职责。

我理解的OAuth是:职权投影。

在取得终端用户同意(授权过程)后,获得用户在服务提供处托管的部分权限。

 

(本节以下部分适合对OAuth协议有一定了解后看,如果是第一次接触OAuth,建议跳过此部分,读完全文回头看这段。)

关于OAuth1.0a和OAuth1.0:

维基百科中说到“2009年4月23日,OAuth宣告了一个1.0协议的安全漏洞。该漏洞影响了OAuth 1.0核心规范第6节的OAuth的认证流程(也称作3阶段OAuth)于是,发布了OAuth Core协议1.0a版本来解决这一问题。”

乌云平台公布过街旁网(在OAuth中角色为第三方应用)使用新浪微博(角色为服务提供者)OAuth授权时的漏洞。该漏洞在1.0a协议中得以解决,见此文中描述如下:

1. oauth_callback 在第一步即获取 Request Token 时( Get Request Token ,图中第二步)就必须提供,如果不需要重定向,则其值必须为 oob ( out_of_band configuration ),服务提供方此时返回参数 oauth_callback_confirmed ,且值为 true 。在第二步即验证阶段(图中第三步)不接受该参数。

2. 验证完成后会返回验证码( oauth_verifier )为的是在没有 callback 的时候,服务提供方显示给用户,然后用户可以在第三方应用的设备上输入,标示自己已经授权(和未授权的用户分开),然后第三方应用必须加上该验证码去获取 Access Token。

 

由于各个平台对协议的实现有些细微的差别,不要奇怪,就像W3C标准之下各个浏览器的不兼容一样,很多地方会遇到平台之间不一致的实现。

比如规避这个漏洞的方式,腾讯微博采用的是不验证callback合法性,但是callback参数是在STEP1(这一步在第三方应用处执行,在授权流程一节中我们会看到)中确定,所以在腾讯微博开放平台注册应用时,不需要填写“回调地址”,而只需填写“应用地址”。

可以随便改换callback参数值,但是不会产生安全问题,因为这个参数是由第三方应用确定的。其他平台(比如新浪微博、腾讯互联)各有不同的实现,在其他文章再讲。

关于OAuth1.0/1.0a和OAuth2.0:

OAuth2.0是OAuth1.0a之后的版本,不向后兼容OAuth 1.0/1.0a,流程更为简单。

我多么希望这一句话就能说完他们的关系,可惜事与愿违。

版本更迭的过程中,总会有各种各样的原因来搅局,维基百科中提到“Facebook的新的Graph API只支持OAuth 2.0,Google在2011年3月亦宣布Google API对OAuth 2.0的支援,Windows Live 亦支援 OAuth 2.0“,但是在国内,只是“看上去很美”。

OAuth1.0/1.0a获取到的AccessToken时效为永久,直到用户取消授权/应用被注销。但是OAuth2.0中AccessToken开始变为时效性(每个平台有效时间不同),同时协议中规定RefreshToken的实现,在AccessToken失效后,使用RefreshToken去获取新的AccessToken。

在这一点的实现上,各个平台出现了巨大的分歧,以下简单列出几个平台关于OAuth2.0的实现。

新浪微博开放平台

没有RefreshToken,AccessToken过期时间如下图

image

过期了咋办?答案是:凉拌。重走授权流程。

QQ互联开放平台

没有RefreshToken,AccessToken过期时间为三个月

官方文档中原话为“access token由每次用户登录时生成,过期时间默认为三个月,用户再次登录时自动刷新,请网站或应用做好防过期策略,或过期后提示用户再次授权”。此处的登陆我认为就是重新授权,因为QQ互联把OAuth授权流程成为“QQ登陆”。expires_in返回的时间也是代表三个月的7776000,但我未曾验证。

腾讯微博开放平台

没有RefreshToken(官方原话为“目前暂不支持”),AccessToken过期时间如下图

image

人人网开放平台

永久生效的ResfreshToken,AccessToken过期时间从文档中的示例代码来看应该是一天("expires_in": 87063)。

开心网开放平台

滑动过期机制,官方原话为:

开心网开放平台的应用,无论通过OAuth2.0哪种方式获取Access Token,都会拿到一个有效期为1个月的Access Token和有效期为2个月的Refresh Token。对于这些应用,只要用户在两个月内登录,应用就可以使用Refresh Token刷新以获得新的Access Token(新的Refresh Token也会同时下发),因此用户只要在两个月内登录过你的应用,就不需要重新登录。

关于腾讯微博开放平台和QQ互联开放平台:

简单地说,QQ互联开放平台包括部分微博方面的功能,部分空间的功能,看这里

你可能会发现域名从connect.qq.com跳到了wiki.opensns什么的,不要奇怪。

我本来以为腾讯这么霸气,可能会有五六个开放平台。

后来发现…………这里能看到23个。。

二、准备工作

1.注册腾讯微博

2.在腾讯微博开放平台点击导航上的“我的应用”,如果没有获得开发者身份,填写资料获得开发者身份

3.创建应用,这里以常见的网页应用为例

在应用通过审核后,你发微博下面会有”来自XXX“,也就是常说的“来源”,会连接到这里填写的应用网址(此地址并非回调地址,腾讯微博开放平台没有这个填写项)。

Z4~$8)_CD[J}YJO_(MT@753

4.拿到AppKEY和AppSecret

AppKEY对应OAuth1.0文档中的Consumer Key,AppSecret对应Consumer Secret(在其他平台也有叫AppID和AppKey的,或者clientID和clientKey的)。

不管名称如何,在实现中这两个值的作用都是差不多的。

AppKey(一般是数字形式,相当于唯一标识)不用保密,在授权流程中也会出现在GET参数中。App Secret必须保密!我在这里直接截图出来因为是示例,我用小号建的,大家如果谁不想注册,只想直接开始,就用我贴出的就可以。

APPKEY:801165332

AppSecret:457bcc49f2a6694208820d2e4b16bf9e

image

三、授权流程(附代码)

这是雅虎提供的流程图,把注册应用获得AppKey和AppSecret也算到step1里面了。

列出了第三方应用、服务提供方、用户的详细动作,可以参考一下。

不过不要死记这个Step归类,OAuth官方和各个平台的划分方法各有不同,容易记混。

314d6612-3c76-37d8-8e73-d9f3d23d614c

 

接着按照我的理解,从第三方应用(client、开发者接入)角度来讲述一下授权流程。

其间附上核心代码,在文章最后提供完整项目示例。

Step1:请求未授权的RequestToken

OAuth1.0授权最最重要的参数是oauth_signature,也就是签名值,不单在授权流程,在请求OAuth1.0授权下的功能接口时,也都要用到。

由以下三个部分确定。

1.签名方法:目前大多数平台使用的都是“HMAC-SHA1”,.NET中由System.Security.Cryptography命名空间下的HMACSHA1类实现。

2.BaseString:part1 + ”&”+ part2 + “&” + part3

part1:HTTP请求方法,按照每个接口的规定选择GET/POST

part2:UrlEncode(URL),若无特别说明,UrlEncode符合RFC3986文档标准。

part3:UrlEncode(key1 + “=” + UrlEncode(para1) + “&” + key2 + “=” + UrlEncode(para2) + “&” + key3 + “=” +  UrlEncode(para3) …)

part3是QueryString的形式(key1=value1&key2=value2)的参数组合,包括本次请求除oauth_signature外的所有参数。

肯定会包括在每次请求中的主要是OAuth相关的,oauth_consumer_key(AppID/AppKey/Client_ID),oauth_signature_method(签名方法),oauth_timestamp(时间戳),oauth_nonce(随机生成的单次值),oauth_version(OAuth版本),oauth_signature(计算BaseString时要排除在外,因为BaseString是用来生成oauth_signature的,这是一个鸡生蛋的问题)。其他的普通参数看具体接口而定,一般都会有一个列表。

官方给的BaseString伪码如下:

httpMethod + "&" + url_encode( base_uri ) + "&" + sorted_query_params.each { | k, v | url_encode ( k ) + "%3D" + url_encode ( v ) }.join("%26")其中part3部分把key部分(k)也编码了,v部分不管是否需要,也都全部做了编码。实际使用中,只要确定key部分,和有些value部分的字符都在以下这个范围的话,就可以不编码。“abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~”。


一点题外话,BaseString是计算签名值时最容易出错的地方,腾讯微博在签名出错时也只有401错误。新浪平台在这一点做得很好,在ResponseHeader中返回了服务器端组合出的BaseString和计算出的签名值,起码有个参考。

3.签名密钥:AppSecret + “&” + TokenSecret

AppSecret就是在我们在第二节中“准备工作”中提到过的。

TokenSecret的值有两种情况,一种是在授权流程中Step1这一步,此时的TokenSecret是Step1返回的oauth_token_secret参数。

另外一种情况就是Step3返回的oauth_token_secret(和授权后的Token,也就是AccessToken一起返回)。

 

说到这儿,你可能疑惑,在第一次发起请求的时候,手里面只有AppKey和AppSecret啊,这时候密钥中的TokenSecret从哪儿弄?

答案是留空就可以了,表达式依然是AppSecret + “&” + TokenSecret,Step1时TokenSecret值是空的。

但是,”&”符号必须保留。这也体现了OAuth的授权的一个方式,就是Step by Step,你后面需要的,前面总会给你。

另外,你可能对OAuthToken、RequestToken、AccessToken这三个名词比较迷惑,这里我是这样理解的:RequestToken会出现两次,是在授权流程中出现的,一段时间后就会失效。AccessToken在OAuth1.0/1.0a中是不会自然失效的。

(平台返回的时候,使用的参数值都是oauth_token,不管是授权前/后或者AccessToken,都是这个key值)。

 

弄清楚边边角角,核心代码是比较简单的,Util是用到工具类,在本文最后会提供。

string RequestUrl = "http://open.t.qq.com/cgi-bin/request_token";
string oauth_signature = "";
string result = "";

//Step1,构造请求获取未授权的RequestToken
Dictionary<string, string> Paras = new Dictionary<string, string>();

Paras.Add("oauth_version", "1.0");
Paras.Add("oauth_signature_method", "HMAC-SHA1");
Paras.Add("oauth_timestamp", Util.GetTimeStamp(true));//腾讯微博使用的是UTC时间不是本地时间
Paras.Add("oauth_nonce", Util.GetNonce());
Paras.Add("oauth_consumer_key", WebConfigurationManager.AppSettings["AppKey"]);
Paras.Add("oauth_callback", Util.RFC3986_UrlEncode(WebConfigurationManager.AppSettings["CallBack"]));

oauth_signature = Util.CreateOauthSignature(Paras, RequestUrl, "GET", WebConfigurationManager.AppSettings["AppSecret"], string.Empty);

Paras.Add("oauth_signature" , Util.RFC3986_UrlEncode(oauth_signature));

result = Util.HttpGet(RequestUrl + "?" + Paras.ToQueryString());

Step2:使用获取到的未授权的RequestToken作为GET参数,重定向到腾讯的用户确认页面

在此页面,如果用户未在腾讯登陆,需要登陆,登陆后还需点击一下“授权”按钮。

这一步没想到有什么要解释的0.0。

Session["oauth_token_secret"] = result.Split('&')[1].Split('=')[1];
//Step2,使用未授权的RequestToken作为Get参数跳转到腾讯微博的授权页面
Response.Redirect("http://open.t.qq.com/cgi-bin/authorize?oauth_token=" + result.Split('&')[0].Split('=')[1]);

result为Step1的返回值,形式为“oauth_token=hdk48Djdsa&oauth_token_secret=xyz4992k83j47x0b&oauth_callback_confirmed=true”。

image

Step3:处理从腾讯重定向过来的请求,用授权后的RequestToken换取AccessToken

首先你可能会注意到接收到的oauth_token有点眼熟,没错,就是第一步获得的RequestToken。在第一步时未授权,在这里是已授权。

这里的方式和第一步时大同小异,只是计算BaseString时和发起请求时多了两个参数,oauth_verifier和oauth_token。

//Step3,解析返回到回调的参数
string RequestUrl = "http://open.t.qq.com/cgi-bin/access_token";
string oauth_signature = "";
string result = "";

string oauth_token = Request.QueryString["oauth_token"];
string oauth_verifier = Request.QueryString["oauth_verifier"];
string openid = Request.QueryString["openid"];//必须留着和之后获得的AccessToken持久化存储,因为是示例就暂时保存到Session中了
string openkey = Request.QueryString["openkey"];//没什么用,是另外一套授权体系的

Dictionary<string, string> Paras = new Dictionary<string, string>();

Paras.Add("oauth_version", "1.0");
Paras.Add("oauth_signature_method", "HMAC-SHA1");
Paras.Add("oauth_timestamp", Util.GetTimeStamp(true));//腾讯微博使用的是UTC时间不是本地时间
Paras.Add("oauth_nonce", Util.GetNonce());
Paras.Add("oauth_consumer_key", WebConfigurationManager.AppSettings["AppKey"]);
Paras.Add("oauth_verifier", oauth_verifier);
Paras.Add("oauth_token", oauth_token);

oauth_signature = Util.CreateOauthSignature(Paras, RequestUrl, "GET", WebConfigurationManager.AppSettings["AppSecret"], Session["oauth_token_secret"].ToString());

Paras.Add("oauth_signature", Util.RFC3986_UrlEncode(oauth_signature));

result = Util.HttpGet(RequestUrl + "?" + Paras.ToQueryString());

result返回值的形式为“oauth_token=nnch734d00ls2jdk&oauth_token_secret=pdkkdhi9sl3r4s00”。

 

oauth_token此时就是AccessToken,和oauth_token_secret(此时就是AccessTokenSecret)一起持久化存储起来吧。

腾讯微博开放平台需要存储的有AppKey、AppSecret、AccessToken、AccessTokenSecret、OpenID。

OpenID是OAuth协议之外的,一个OpenID唯一对应一个QQ号,可能腾讯是考虑不泄露QQ号,或者是别的原因。

四、需要注意的细节

1.编码方式使用RFC3986文档中规定的UrlEncode,不使用不一定出问题,使用了一定不在这里出问题。

2.腾讯微博开放平台,修改密码会导致用户的所有授权失效。

3.https和http,因为涉及到计算签名时候的part2(URL),所以要注意如果请求的是https地址,那么签名时候也要用https的地址,反之亦然。

 

本文以腾讯微博开放平台为例讲述了OAuth1.0的授权流程,对于其他平台的OAuth1.0实现,有很大程度上的参考意义。

最后附上本文的完整示例代码

posted @ 2012-06-25 16:15  空葫芦  阅读(9979)  评论(7编辑  收藏  举报