OAuth 2.0 开发完全详解
--------------------------基础篇-------------------------------
I:OAuth 2.0 概述
首先大家来看看国内新浪跟腾讯这两大头对OAuth 2.0是怎么个用官方语言去阐述的:
新浪:OAuth2.0是从2006年开始设计OAuth协议的下一个版本,OAuth2.0同时提供Web,桌面和移动应用程序的支持,并较1.0相比整个授权验证流程更简单更安全。也是新浪微博开放平台未来最主要的用户身份验证和授权方式。
腾讯:腾讯微博开放平台,是基于腾讯微博系统,为广大开发者和用户提供的开放数据分享与传播平台。
广大开发者和用户登录平台后,就可以使用平台提供的开放API接口,创建应用从微博系统获取信息,或将新的信息传播到整个微博系统中,丰富多样的API接口和应用,加上您的智慧,将创造出无穷的应用和乐趣。
在使用腾讯微博平台提供的API前,您需要做以下两步工作:
成为开发者,并申请appkey和appsecret
授权获取accesstoken
Accesstoken是第三方获得用户授权的凭证,是第三方访问api资源的票据。目前,腾讯微博采用Oauth2.0协议对第三方进行授权,相对Oauth1.0来说,Oauth2.0具有更加简单和安全特点。
相比之下我说一下我个人的理解!OAuth是一种允许你的应用访问到腾讯微博,新浪微博开放平台上的用户数据,而且是在不需要取得用户个人密码的情况下。所以可以说OAuth 2.0是一个互联网标准协议(OAuth 2.0基于https)。
II:OAuth 2.0在国内支持的授权方式
1.Authorization Code方式(新浪,腾讯都支持)
Authorization Code授权方式是专门提供给第三方WEB应用的。目前国内的新浪微博,腾讯开放平台都支持Authorization Code授权方式,下面我将发布一些图片让大家对于这种授权方式有一种更为直观的了解。
进入我的测试站时。如果用户已经有了新浪微博或腾讯微博的账户那么他可以直接点击下面的链接进行跳转授权验证。如下图
从上图的地址栏信息可以知道~~用户的密码始终没有对我的第三方应用暴露。
当用户对我的第三方应用授权了以后,我将可以很轻松地读取用户个人信息和发送测试微博。
腾讯接口返回的用户信息
新浪微博返回的用户信息
发送的测试微博信息
以上的登陆授权示例就是我推荐使用的Authorization Code授权方式
在这个模式下AccessToken没有透露在地址栏上~用户密码也没有暴露在我的第三方应用当中!这里可以看出OAuth2.0相比OAuth1.0来说更简单,更安全(https)
2.Resource Owner Password Credentials方式(新浪支持)
楼主不研究这个,因为这个是新浪提供的。腾讯微博未发现支持这种这么犀利的验证方式,居然允许第三方应用取得用户密码。。。。
3.Implicit Grant方式(新浪,腾讯都支持)
楼主也不研究这个,因为这个方式允许AccessToken暴露在用户的Url上。
III:总结
好了,博主dotNetDR_目前只在Web开发当中运用到OAuth 2.0,所以这个系列往后也只涉及OAuth2.0里面的Authorization Code验证授权模式,除了它以外的OAuth 2.0其他授权模式各位同学有需要的话请自行研究。
项目代码截图
---------------------------------核心篇-------------------------------------------
I:OAuth 2.0 开发前期准备
天上不会自然掉馅饼让你轻松地去访问到人家资源服务器里面的用户数据资源,所以你需要做的前期开发准备工作就是把AppKey, AppSecret取到手 新浪获取传送门,腾讯获取传送门
这里说一下,在申请AppKey和AppSecret的过程中,新浪和腾讯的申请做法是有区别的。
在新浪微博的AppKey,AppSecret申请时会验证你是否拥有域名的所有权
而腾讯在这一块上面则没有这个要求!
PS:申请成为开放平台开发者时需要上传身份证电子文件。。。。。
II:为什么不用官方提供的SDK
说到这个我就想吐槽了,这官方的SDK尼玛的明显排斥堆挤咋们做.net的啊!~~~
先上新浪支持的SDK:
然后在上腾讯支持的SDK:
文档资料不全不说,出了问题你还得找人家。所以在这里我也试想过转战JS SDK看看~于是又有了如下的悲剧事情发生:
腾讯和新浪的JS SDK都是主推用js弹窗方面的。这样不太会电脑的用户使用起来的话,就会觉得你的这个第三方应用会不会是病毒神马的。
IE9下弹窗提示
Chrome下也会提示,所以这个东西是浏览器本身机制的问题~所以在帖子里面也得到了准确的答复。
稍微设置一下允许弹窗的话就得到上面这个怪异摸样。。。
而在这里稍微说一下腾讯的OPENJS这个东西!!我个人感觉它想挑战一下我们开发人员的智商。。。
这个为什么浏览器没有阻止,完全是在同域的情况下啊~~~TX你这互联老大连另外整个类似于新浪的独立域名的工作都没做好啊!!还在自家的API文档站上高亮标示起这个OpenJS新秀呀。
不过相比新浪的JS SDK腾讯自家的OpenJS的技术支持做得非常好的。你只要碰到了问题。都有人在线帮你解答。
PS:如果你选用JS SDK的话,那么你的业务逻辑将会以js脚本的形式暴露在客户端浏览器之下。
III:Authorization Code验证授权模式
基础知识:
在这里先引用前一篇文章里的示例用图,然后再接着讲解各个部分的相关知识。
1.Resource Server(资源服务器):负责存放服务提供商的用户数据资源等相关信息。当第三方应用访问这个资源服务器时,需要提供Access Token否则会提示访问失败。所以我们最终的目的就是让自己开发的第三方Web应用顺利地访问到服务提供商的资源服务器,这才是这个系列文章的最终目的。
2.Authorization Server(验证授权服务器):负责验证用户账户名密码,以及给第三方WEB应用发放Access Token。在这里我上传两张图片为你叙述Authorization Server是什么样子。
新浪的Authorization Server
腾讯的Authorization Server
接下来将会继续讲解,这个重要的Access Token(访问令牌)到底是怎么取得的。
首先作为第三方网站上会显示一个跳转到新浪,腾讯授权服务器的<a />超级链接。如下图:
下面的图片将介绍这两个链接的跳转地址规范:
新浪的规范
https://api.weibo.com/oauth2/authorize?client_id={AppKey}&response_type=code&redirect_uri={YourSiteUrl}
腾讯的规范
https://open.t.qq.com/cgi-bin/oauth2/authorize?client_id={AppKey}&response_type=code&redirect_uri={YourSiteUrl}
可以看出新浪和腾讯的规范在此步骤基本一致。
现在讲述第2步:
这时Authorization Server将会跳转回申请授权验证的第三方网站~但是会在QueryString内加上一个名为code的参数!例子如下:
腾讯:http://www.mytestsite.com/Tencent.aspx?code=174256357036c9df7db17342f15a9476&openid=45CD8A7A05A0C3E30D8A9AB74EEAA8D1&openkey=98B2964245A2BE2830F7A793E09FE6B0
新浪:http://www.mytestsite.com/Sina.aspx?code=19b83321705c538e0422ba09ac9043a0
从这一步可以看出企鹅与标准脱离的野心逐渐浮现。。。它不仅仅返回code而且还参杂openid&openkey~不知在各位开发者的眼里会不会觉得比较另类?
当我们拿到跳转回来Url上的QueryString参数code后就可以再次去Authorization Server上请求获取AccessToken这个重要令牌了!下面接着上图!!!
具体说一下第3步的请求地址规范:
新浪:https://api.weibo.com/oauth2/access_token?client_id={AppKey}&client_secret={AppSecret}&grant_type=authorization_code&redirect_uri={YourSiteUrl}&code={code}
腾讯:https://open.t.qq.com/cgi-bin/oauth2/access_token?client_id={AppKey}&client_secret={AppSecret}&redirect_uri={YourSiteUrl}&grant_type=authorization_code&code={code}
在这一步,腾讯和新浪双方都完全保持一致,非常庆幸!
第4步,重点来了,授权服务器即将返回Access Token。这是以Response Body的方式,所以说Authorization Code授权方式并没有对客户端暴露AccessToken访问令牌。也是我极力推荐使用的一种授权方式。
上图是新浪返回Access Token的内容
上图是腾讯返回Access Token的内容
这里需要注意一下第3,4步必须要以http post的方式去发起Request。~
IV:总结
-----------------------------------------应用篇------------------------------------
I:访问资源服务器需要些什么?
访问资源服务器最最重要的前提条件就是你必须要有Access Token。而关于Access Token的取得原理已经在前面两篇(第1篇、第2篇)随笔当中有介绍,在这里就略过介绍Access Token的获取方法了。
首先我们还是先看看新浪、腾讯他们的API文档上提供的参考内容。
新浪资源服务器API调用规范:
也就是说新浪目前提供两种方式去调用API.
第一种是以QueryString参数的形式去调用,例子:
https://api.weibo.com/2/{API接口}?access_token={yourAccessToken}
第二种是往header里添加Authorization:OAuth2 {yourAccessToken}的形式去调用。
腾讯资源服务器API调用规范:
根据腾讯方的文档资料来看!腾讯目前提供的API调用方法跟新浪的第一种方式类似,但没有提供类似新浪第二种把Access Token放到Header里面的方式(或许有但是我未能从文档站上搜索到具体要求)。接着GRD的腾讯又搞了几个属于自己的参数如:openid, clientip, oauth_version,微微地创新了一把!关于OpenID腾讯的介绍是OpenID可以唯一标识一个用户。在同一个应用下,同一个QQ号码的OpenID是相同的;但在不同应用下,同一个QQ号码可能有不同的OpenID。另外楼主可以推断这个oauth_version参数的设计能够证明目前腾讯接口不是很稳定,到时候估计会闹2.b/2.c/2.d/2.e/2.*,或者过一定时间后oauth.net推出3.0版本时,可以通过修改oauth_version去支持。
腾讯的例子:
https://open.t.qq.com/api/{API接口}?oauth_consumer_key={AppKey}&access_token={AccessToken}&openid={Openid}&clientip={ClientIP}&oauth_version=2.a&scope=all
在这说一下,腾讯的参数中客户端ip(clientip)可以不填(博主未确认应用部署上线后是否需要提供,因为应用都在开发期。。。)
ok,关于腾讯跟新浪的资源服务器API调用规范的重要部分已经介绍完毕了。接下来放送新浪腾讯API列表,因为调用他们的API不单单只是提供Access Token还需要根据接口的说明文档区确认是GET还是POST那些参数是可选的,那些参数是必选的之类。
II:访问资源服务器API示例
首先下载dotNetDR_OAuth2程序集 codeplex下载地址(介绍)
然后新建一个MVC3的应用程序!将刚刚下载的OAuth2组件引用进来。 (WebForm示例)
接着在项目代码文件里添加你的AppKey, AppSecret
然后再Home控制器的Index Action加上跳转到新浪和腾讯微博授权页面的超级链接。
HomeController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Dynamic;
//引入dotNetDR_OAuth2组件命名空间
using dotNetDR_OAuth2;
using dotNetDR_OAuth2.AccessToken;
namespace dotNetDR_OAuth2.Sample.MVC.Controllers
{
public class HomeController : Controller
{
//获取新浪、腾讯的IAuthorizationCodeBase接口实例
private IAuthorizationCodeBase sina = AccessTokenFactory.Create(DefaultAppConfigs.Sina);
private IAuthorizationCodeBase tencent = AccessTokenFactory.Create(DefaultAppConfigs.Tencent);
public ActionResult Index()
{
dynamic model = new ExpandoObject();
//生成主机头例如:http://www.yourhost.com:8081 (注:默认80端口则不会显示:80)
var hostPath = AccessTokenToolkit.GenerateHostPath(Request.Url);
//定义授权成功后返回的url地址
var sinaRedirectUrl = hostPath + Url.Action("Index", "Sina");
var tencentRedirectUrl = hostPath + Url.Action("Index", "Tencent");
//设置超级链接
model.SinaLink = sina.GenerateCodeUrl(sinaRedirectUrl);
model.TencentLink = tencent.GenerateCodeUrl(tencentRedirectUrl);
return View(model);
}
public ActionResult About()
{
return View();
}
}
}
然后Index.cshtml:
@{
ViewBag.Title = "Home Page";
}
@model dynamic
<h2>dotNetDR_OAuth2 微博API访问组件示例</h2>
<div>
@if (Model != null)
{
<a href="@Model.SinaLink"><img src="http://www.cnblogs.com/Content/Images/xlwb.gif" />新浪微博登陆</a><text>|</text>
<a href="@Model.TencentLink"><img src="http://www.cnblogs.com/Content/Images/txwb.gif" />腾讯微博登陆</a>
}
else
{
<h3>Error: Model没有值</h3>
}
</div>
上图是效果图
接着我们建立各自的实现:SinaController, TencentController.
新浪部分 - SinaController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
//导入组件命名空间
using dotNetDR_OAuth2;
using dotNetDR_OAuth2.AccessToken;
using dotNetDR_OAuth2.APIs.Providers.Sina;
namespace dotNetDR_OAuth2.Sample.MVC.Controllers
{
public class SinaController : Controller
{
private IAuthorizationCodeBase _authCode = AccessTokenFactory.Create(DefaultAppConfigs.Sina);
public ActionResult Index(string code)
{
if (Session["accessToken"] == null)
{
if (!string.IsNullOrEmpty(code))
{
var redirectUrl = AccessTokenToolkit.GenerateHostPath(Request.Url) + Url.Action("Index");
var accessToken = _authCode.GetResult(_authCode.GenerateAccessTokenUrl(redirectUrl, code));
if (Session["accessToken"] != null)
{
Session.Remove("accessToken");
}
Session.Add("accessToken", accessToken);
var hasAccessToken = new object();
return View(hasAccessToken);
}
else
{
return GotoIndex();
}
}
return View(new object());
}
public ActionResult ShowUserInfo()
{
if (Session["accessToken"] == null)
{
return GotoIndex();
}
var accessTokenObj = Session["accessToken"] as dynamic;
var uid = accessTokenObj.uid;
var accessToken = accessTokenObj.access_token;
var model = SinaApi.CallGet("users/show.json?uid=" + uid, accessToken);
SinaError err;
if (!SinaApi.HasError(model, out err))
{
return View(model);
}
else
{
Session["err"] = err;
return RedirectToAction("Error");
}
}
public ActionResult PublishMsg()
{
if (Session["accessToken"] == null)
{
return GotoIndex();
}
var accessTokenObj = Session["accessToken"] as dynamic;
var uid = accessTokenObj.uid;
var accessToken = accessTokenObj.access_token;
var msg = "Time: " + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fffff") + ": 这是一条来自dotNetDR_OAuth2组件发出的1条测试微博信息!";
var formData = new Dictionary<string, string>();
formData.Add("status", Server.UrlEncode(msg));
SinaError err;
var result = SinaApi.CallPost("statuses/update.json", accessToken, formData);
if (!SinaApi.HasError(result, out err))
{
return View();
}
else
{
Session["err"] = err;
return RedirectToAction("Error");
}
}
public ActionResult Error()
{
var err = Session["err"] as SinaError;
return View(err);
}
#region NonAction
[NonAction]
private ActionResult GotoIndex()
{
return RedirectToAction("Index", "Home");
}
#endregion
}
}
Index.cshtml:
@{
ViewBag.Title = "Index";
}
@model object
<h2>操作</h2>
<p>@Html.ActionLink("返回", "Index", "Home")</p>
@if (Model != null)
{
@Html.ActionLink("显示用户信息", "ShowUserInfo") <text> | </text>
@Html.ActionLink("发送测试微博", "PublishMsg")
}
PublishMsg.cshtml:
@{
ViewBag.Title = "PublishMsg";
}
<h2>发送成功</h2>
ShowUserInfo.cshtml:
@{
ViewBag.Title = "ShowUserInfo";
}
<h2>用户:@Model.screen_name</h2>
<p>
用户UID: @Model.id<br />
用户昵称: @Model.screen_name<br />
友好显示名称: @Model.name<br />
用户所在地区ID: @Model.province<br />
用户所在城市ID: @Model.city<br />
用户所在地: @Model.location<br />
用户描述: @Model.description<br />
用户博客地址: @Model.url<br />
用户头像地址: @Model.profile_image_url @MvcHtmlString.Create(string.Format("<img src='{0}' />", Model.profile_image_url))<br />
用户的个性化域名: @Model.domain<br />
性别(m:男、f:女、n:未知): @Model.gender<br />
粉丝数: @Model.followers_count<br />
关注数: @Model.friends_count<br />
微博数: @Model.statuses_count<br />
收藏数: @Model.favourites_count<br />
创建时间: @Model.created_at<br />
当前登录用户是否已关注该用户: @Model.following<br />
是否允许所有人给我发私信: @Model.allow_all_act_msg<br />
是否允许带有地理信息: @Model.geo_enabled<br />
是否是微博认证用户,即带V用户: @Model.verified<br />
是否允许所有人对我的微博进行评论: @Model.allow_all_comment<br />
用户大头像地址: @Model.avatar_large @MvcHtmlString.Create(string.Format("<img src='{0}' />", @Model.avatar_large))<br />
认证原因: @Model.verified_reason<br />
该用户是否关注当前登录用户: @Model.follow_me<br />
用户的在线状态,0:不在线、1:在线: @Model.online_status<br />
用户的互粉数: @Model.bi_followers_count<br />
</p>
Error.cshtml:
@{
ViewBag.Title = "Error";
}
@model dotNetDR_OAuth2.APIs.Providers.Sina.SinaError
<h2>Error</h2>
<p>
error_code: @Model.error_code<br />
error: @Model.error<br />
request: @Model.request<br />
</p>
腾讯部分 - TencentController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using dotNetDR_OAuth2;
using dotNetDR_OAuth2.AccessToken;
using dotNetDR_OAuth2.APIs.Providers.Tencent;
namespace dotNetDR_OAuth2.Sample.MVC.Controllers
{
public class TencentController : Controller
{
private IAuthorizationCodeBase _authCode = AccessTokenFactory.Create(DefaultAppConfigs.Tencent);
//
// GET: /Tencent/
public ActionResult Index(string code, string openid, string openkey)
{
if (Session["accessToken"] == null)
{
if (!string.IsNullOrEmpty(code))
{
var redirectUrl = AccessTokenToolkit.GenerateHostPath(Request.Url) + Url.Action("Index");
var accessToken = _authCode.GetResult(_authCode.GenerateAccessTokenUrl(redirectUrl, code));
if (Session["accessToken"] != null)
{
Session.Remove("accessToken");
}
accessToken.openid = openid; //注意GRD腾讯自家的微创新
accessToken.openkey = openkey; //注意GRD腾讯自家的微创新
Session.Add("accessToken", accessToken);
var hasAccessToken = new object();
return View(hasAccessToken);
}
else
{
return GotoIndex();
}
}
return View(new object());
}
public ActionResult ShowUserInfo()
{
if (Session["accessToken"] == null)
{
return GotoIndex();
}
var accessTokenObj = Session["accessToken"] as dynamic;
var uid = accessTokenObj.name;
var accessToken = accessTokenObj.access_token;
var openid = accessTokenObj.openid;
var model = TencentApi.CallGet("user/info?format=json", accessToken, openid);
TencentError err;
if (!TencentApi.HasError(model, out err))
{
var realModel = model.data;
return View(realModel);
}
else
{
Session["err"] = err;
return RedirectToAction("Error");
}
}
public ActionResult PublishMsg()
{
if (Session["accessToken"] == null)
{
return GotoIndex();
}
var accessTokenObj = Session["accessToken"] as dynamic;
var uid = accessTokenObj.name;
var accessToken = accessTokenObj.access_token;
var openid = accessTokenObj.openid;
var msg = "Time: " + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fffff") + ": 这是一条来自dotNetDR_OAuth2组件发出的1条测试微博信息!";
var formData = new Dictionary<string, string>();
formData.Add("content", Server.UrlEncode(msg));
TencentError err;
var result = TencentApi.CallPost("t/add?format=json", accessToken, openid, formData);
if (!TencentApi.HasError(result, out err))
{
return View();
}
else
{
Session["err"] = err;
return RedirectToAction("Error");
}
}
public ActionResult Error()
{
var err = Session["err"] as TencentError;
return View(err);
}
#region NonAction
[NonAction]
private ActionResult GotoIndex()
{
return RedirectToAction("Index", "Home");
}
#endregion
}
}
Index.cshtml:
@{
ViewBag.Title = "Index";
}
@model object
<h2>操作</h2>
<p>@Html.ActionLink("返回", "Index", "Home")</p>
@if (Model != null)
{
@Html.ActionLink("显示用户信息", "ShowUserInfo") <text> | </text>
@Html.ActionLink("发送测试微博", "PublishMsg")
}
PublishMsg.cshtml:
@{
ViewBag.Title = "PublishMsg";
}
<h2>发送成功</h2>
ShowUserInfo.cshtml:
@{
ViewBag.Title = "ShowUserInfo";
}
<h2>用户昵称: @Model.nick</h2>
<p>
出生天: @Model.birth_day <br />
出生月: @Model.birth_month <br />
出生年: @Model.birth_year <br />
城市id: @Model.city_code <br />
国家id: @Model.country_code <br />
邮箱: @Model.email <br />
听众数: @Model.fansnum <br />
收藏数: @Model.favnum <br />
头像url: @Model.head @MvcHtmlString.Create(string.Format("<img src=\"{0}/50\" />", @Model.head)) <br />
家乡所在城市id: @Model.homecity_code <br />
家乡所在国家id: @Model.homecountry_code <br />
个人主页: @Model.homepage <br />
家乡所在省id: @Model.homeprovince_code <br />
家乡所在城镇id: @Model.hometown_code <br />
收听的人数: @Model.idolnum <br />
行业id: @Model.industry_code <br />
个人介绍: @Model.introduction <br />
是否企业机构: @Model.isent <br />
是否在当前用户的黑名单中,0-不是,1-是: @Model.ismyblack <br />
是否是当前用户的听众,0-不是,1-是: @Model.ismyfans <br />
是否是当前用户的偶像,0-不是,1-是: @Model.ismyidol <br />
是否实名认证,0-老用户,1-已实名认证,2-未实名认证: @Model.isrealname <br />
是否认证用户: @Model.isvip <br />
所在地: @Model.location <br />
互听好友数: @Model.mutual_fans_num <br />
用户帐户名: @Model.name <br />
用户唯一id,与name相对应: @Model.openid <br />
地区id: @Model.province_code <br />
注册时间: @Model.regtime <br />
是否允许所有人给当前用户发私信,0-仅有偶像,1-名人+听众,2-所有人: @Model.send_private_flag <br />
用户性别,1-男,2-女,0-未填写: @Model.sex <br />
发表的微博数: @Model.tweetnum <br />
认证信息: @Model.verifyinfo <br />
</p>
Error.cshtml:
@{
ViewBag.Title = "Error";
}
@model dotNetDR_OAuth2.APIs.Providers.Tencent.TencentError
<h2>Error</h2>
<p>
ret: @Model.ret<br />
errcode: @Model.errcode<br />
msg: @Model.msg<br />
----------------------<br />
errcode=1 无效TOKEN,被吊销<br />
errcode=2 请求重放<br />
errcode=3 access_token不存在<br />
errcode=4 access_token超时<br />
errcode=5 oauth 版本不对<br />
errcode=6 oauth 签名方法不对<br />
errcode=7 参数错<br />
errcode=8 处理失败<br />
errcode=9 验证签名失败<br />
errcode=10 网络错误<br />
errcode=11 参数长度不对<br />
errcode=12 处理失败<br />
errcode=13 处理失败<br />
errcode=14 处理失败<br />
errcode=15 处理失败<br />
</p>
在这里重复上一下效果图吧!
发送微博的效果我就不贴了!!
这里附上一个各位OAuth开发者或许需要的流程图(专家请尽情喷小菜)
III:dotNetDR_OAuth2 组件介绍
在上一节里面的代码!大家都可以看到这个组件已经隐藏了System.Net.HttpWebRequest, System.Net.HttpWebResponse这些细节,而且返回的值都是dynamic类型的,这样一下。我们就仅需要对这新浪或者腾讯的API文档来逐步调试了,因为博主不可能在组件里把每一个接口返回值得都定义成一个C#类文件:
如果都定义成claas我太累了,所以用.NET 4.0 提供的dynamic算了,更加具体内容我打算另外用一遍随笔去介绍!!转载的请声明及保留好出处!!
组件作者:博客园dotNetDR_ http://www.cnblogs.com/highend/
IV:Access Token的过期时间
新浪:
腾讯:
V:示例项目代码
注意:当你在测试环境下时,必须要把windows系统的hosts文件添加好具体的域名地址指向你本机,例如博主的:
然后就是需要打开dotNetDR_OAuth2.Sample.MVC.csproj手动更改IIS路径
---------------------------------代码下载---------------------似乎、好像、可能 下载不了-----------------------------
----------结束语------------
未删改。