dotnetopenauth mvcwinform环境能用吗

Downloads of v 4.3.4.13329
Average downloads per day
Last published
Share on Social Networks
License details
There is a newer prerelease version of this package available. See the version list below for details.
Add OpenID 1.1/2.0, OAuth 1.0(a), & InfoCard authentication and authorization functionality for client and server applications.
This allows your (web) application to issue identities or accept issued identites from other web applications, and even access your users' data on other services.
To install DotNetOpenAuth, run the following command in the
Install-Package DotNetOpenAuth
-Version 4.3.4.13329
Andrew Arnott
Dependencies
(= 4.3.4.13329)
(= 4.3.4.13329)
(= 4.3.4.13329)
(= 4.3.4.13329)
(= 4.3.4.13329)
(= 4.3.4.13329)
(= 4.3.4.13329)
(= 4.3.4.13329)
(= 4.3.4.13329)
Version History
Last updated
Wednesday, October 23, 2013
Monday, November 25, 2013
Wednesday, October 23, 2013
Sunday, October 20, 2013
Friday, June 28, 2013
Saturday, April 27, 2013
Sunday, February 24, 2013
Sunday, January 27, 2013
Friday, January 25, 2013Dev Guide:
I:访问资源服务器需要些什么?
访问资源服务器最最重要的前提条件就是你必须要有Access Token。而关于Access Token的取得原理已经在前面两篇(、)随笔当中有介绍,在这里就略过介绍Access Token的获取方法了。
首先我们还是先看看新浪、腾讯他们的API文档上提供的参考内容。
也就是说新浪目前提供两种方式去调用API.
第一种是以QueryString参数的形式去调用,例子:
/2/{API接口}?access_token={yourAccessToken}
第二种是往header里添加Authorization:OAuth2 {yourAccessToken}的形式去调用。
根据腾讯方的文档资料来看!腾讯目前提供的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.*,或者过一定时间后推出3.0版本时,可以通过修改oauth_version去支持。
腾讯的例子:
https://open./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程序集 ()
然后新建一个MVC3的应用程序!将刚刚下载的OAuth2组件引用进来。 ()
接着在项目代码文件里添加你的AppKey, AppSecret
然后再Home控制器的Index Action加上跳转到新浪和腾讯微博授权页面的超级链接。
HomeController.cs:
using Susing System.Collections.Gusing System.Lusing System.Wusing System.Web.Musing System.D//引入dotNetDR_OAuth2组件命名空间using dotNetDR_OAuth2;using dotNetDR_OAuth2.AccessTnamespace 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();
//生成主机头例如::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="/Content/Images/xlwb.gif" /&新浪微博登陆&/a&&text&|&/text&
&a href="@Model.TencentLink"&&img src="/Content/Images/txwb.gif" /&腾讯微博登陆&/a&
&h3&Error: Model没有值&/h3&
上图是效果图
接着我们建立各自的实现:SinaController, TencentController.
新浪部分 - SinaController.cs:
using Susing System.Collections.Gusing System.Lusing System.Wusing System.Web.M//导入组件命名空间using dotNetDR_OAuth2;using dotNetDR_OAuth2.AccessTusing dotNetDR_OAuth2.APIs.Providers.Snamespace 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);
return GotoIndex();
return View(new object());
public ActionResult ShowUserInfo()
if (Session["accessToken"] == null)
return GotoIndex();
var accessTokenObj = Session["accessToken"] as
var uid = accessTokenObj.
var accessToken = accessTokenObj.access_
var model = SinaApi.CallGet("users/show.json?uid=" + uid, accessToken);
if (!SinaApi.HasError(model, out err))
return View(model);
Session["err"] =
return RedirectToAction("Error");
public ActionResult PublishMsg()
if (Session["accessToken"] == null)
return GotoIndex();
var accessTokenObj = Session["accessToken"] as
var uid = accessTokenObj.
var accessToken = accessTokenObj.access_
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));
var result = SinaApi.CallPost("statuses/update.json", accessToken, formData);
if (!SinaApi.HasError(result, out err))
return View();
Session["err"] =
return RedirectToAction("Error");
public ActionResult Error()
var err = Session["err"] as SinaE
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 Susing System.Collections.Gusing System.Lusing System.Wusing System.Web.Musing dotNetDR_OAuth2;using dotNetDR_OAuth2.AccessTusing dotNetDR_OAuth2.APIs.Providers.Tnamespace 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 = //注意GRD腾讯自家的微创新
accessToken.openkey = //注意GRD腾讯自家的微创新
Session.Add("accessToken", accessToken);
var hasAccessToken = new object();
return View(hasAccessToken);
return GotoIndex();
return View(new object());
public ActionResult ShowUserInfo()
if (Session["accessToken"] == null)
return GotoIndex();
var accessTokenObj = Session["accessToken"] as
var uid = accessTokenObj.
var accessToken = accessTokenObj.access_
var openid = accessTokenObj.
var model = TencentApi.CallGet("user/info?format=json", accessToken, openid);
if (!TencentApi.HasError(model, out err))
var realModel = model.
return View(realModel);
Session["err"] =
return RedirectToAction("Error");
public ActionResult PublishMsg()
if (Session["accessToken"] == null)
return GotoIndex();
var accessTokenObj = Session["accessToken"] as
var uid = accessTokenObj.
var accessToken = accessTokenObj.access_
var openid = accessTokenObj.
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));
var result = TencentApi.CallPost("t/add?format=json", accessToken, openid, formData);
if (!TencentApi.HasError(result, out err))
return View();
Session["err"] =
return RedirectToAction("Error");
public ActionResult Error()
var err = Session["err"] as TencentE
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_
IV:Access Token的过期时间
V:示例项目代码
注意:当你在测试环境下时,必须要把windows系统的hosts文件添加好具体的域名地址指向你本机,例如博主的:
然后就是需要打开dotNetDR_OAuth2.Sample.MVC.csproj手动更改IIS路径
广告:OAuth2.0 组件讨论群:&询问C#以外的OAuth 2.0 sdk同学勿入!谢谢合作。
本文到此结束!如果觉得文章对你帮助很大的,请点击[推荐]~谢谢。
阅读(...) 评论()DotNetOpenAuth实践系列 - 推酷
DotNetOpenAuth实践系列
本人在研究DotNetOpenAuth的过程中,遇到很多问题,很多坑,花费了很多时间才调通这玩意,现在毫无保留的分享出来,希望博友们可以轻松的上手DotNetOpenAuth,减少爬坑时间。
本系列介绍client_credentials认证方式,并且为了最大程度的展示关键代码,没有使用数据库
.net4.5.1 ,DotNetOpenAuth v5.0.0-alpha3,MVC5
开发工具:
Visual Studio 2015
系列文章目录
摘要: DotNetOpenAuth是OAuth2的.net版本,利用DotNetOpenAuth我们可以轻松的搭建OAuth2验证服务器,不废话,下面我们来一步步搭建验证服务器
摘要: 在上篇中我们搭建了一个简单的认证服务器,里面使用到了Windows签名证书,这一篇则是教大家如何制作Windows签名证书
摘要: 上一篇我们写了一个OAuth2的认证服务器,我们也获取到access_token,那么这个token怎么使用呢,我们现在就来揭开
摘要: 上篇我们讲到WCF服务作为资源服务器接口提供数据服务,那么这篇我们介绍WebApi作为资源服务器
摘要: 上篇我们讲到WebApi资源服务器配置,这篇我们说一下Webform下的ashx,aspx做的接口如何使用OAuth2认证
本系列只简单的讲了一下如何去搭建一个认证服务器和资源服务器,以及搭建的时候的注意点
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
没有分页内容
图片无法显示
视频无法显示
与原文不一致使用DotNetOpenAuth搭建OAuth2.0授权框架 - 莱布尼茨 - 博客园
标题还是一如既往的难取。
我认为对于一个普遍问题,必有对应的一个简洁优美的解决方案。当然这也许只是我的一厢情愿,因为根据宇宙法则,所有事物总归趋于混沌,而OAuth协议就是混沌中的产物,不管是1.0、1.0a还是2.0,单看版本号就让人神伤。
对接过各类开放平台的朋友对OAuth应该不会陌生。当年我,各种token、key、secret、code、id,让我眼花缭乱,不明所以,虽然最终调通,但那种照猫画虎的感觉颇不好受。最近公司计划,开放接口的授权协议从1.0升到2.0,这个任务不巧就落在了我的头上。
声明:我并没有认真阅读过OAuth2.0协议规范,本文对OAuth2.0的阐述或有不当之处,请谅解。本文亦不保证叙述的正确性,欢迎指正。认真的朋友可移步&http://tools.ietf.org/html/rfc6749
OAuth2.0包含四种角色:
用户,又叫资源所有者
客户端,俗称第三方应用
授权服务端,颁发AccessToken
资源服务端,根据AccessToken开放相应的资源访问权限
本文涉及到三种授权模式:
Authorization Code模式:这是现在互联网应用中最常见的授权模式。客户端引导用户在授权服务端输入凭证获取用户授权(AccessToken),进而访问用户资源。需要注意的是,在用户授权后,授权服务端先回传客户端授权码,然后客户端再使用授权码换取AccessToken。为什么不直接返回AccessToken呢?主要是由于用户授权后,授权服务端重定向到客户端地址(必须的,用户可不愿停留在授权服务端或者重新敲地址),此时数据只能通过QueryString方式向客户端传递,在用户浏览器地址栏中可见,不安全,于是分成了两步。第二步由客户端主动请求获取最终的令牌。
Client Credentials Flow:客户端乃是授权服务端的信任合作方,不需要用户参与授权,事先就约定向其开放指定资源(不特定于用户)的访问权限。客户端通过证书或密钥(或其它约定形式)证明自己的身份,获取AccessToken,用于后续访问。
Username and Password Flow:客户端被用户和授权服务端高度信任,用户直接在客户端中输入用户名密码,然后客户端传递用户名密码至授权服务端获取AccessToken,便可访问相应的用户资源。这在内部多系统资源共享、同源系统资源共享等场景下常用,比如单点登录,在登录时就获取了其它系统的AccessToken,避免后续授权,提高了用户体验。关于第四种隐式授权模式,乃是Authorization Code模式省略获取授权码的步骤,直接返回AccessToken,因此会带来一定的安全隐患。不过在某些场景下还是合适的,比如浏览器插件和手机app,不会显式呈现返回的url,所以一定程度上还是安全的。
上述模式涉及到三类凭证:
AuthorizationCode:授权码,授权服务端和客户端之间传输。
AccessToken:访问令牌,授权服务端发给客户端,客户端用它去到资源服务端请求资源。
RefreshToken:刷新令牌,授权服务端和客户端之间传输。
对客户端来说,授权的过程就是获取AccessToken的过程。
总的来说,OAuth并没有新鲜玩意,仍是基于加密、证书诸如此类的技术,在OAuth出来之前,这些东东就已经被大伙玩的差不多了。OAuth给到我们的最大好处就是统一了流程标准,一定程度上促进了互联网的繁荣。
我接到任务后,本着善假于物的理念,先去网上搜了一遍,原本以为有很多资源,结果只搜到DotNetOpenAuth这个开源组件。更让人失望的是,官方API文档没找到(可能是我找的姿势不对,有知道的兄弟告知一声),网上其它资料也少的可怜,其间发现一篇,欣喜若狂,粗粗浏览一遍,有收获,却觉得该组件未免过于繁杂(由于时间紧迫,我并没有深入研究,只是当前观点)。DotNetOpenAuth包含OpenID、OAuth1.0[a]/2.0,自带的例子有几处暗坑,不易(能)调通。下面介绍我在搭建基于该组件的OAuth2.0授权框架时的一些心得体会。
本文介绍的DotNetOpenAuth乃是对应.Net4.0的版本。
授权服务端
授权服务端交道打的最多的就是客户端,于是定义一个Client类,实现DotNetOpenAuth.OAuth2.IClientDescription接口,下面我们来看IClientDescription的定义:
public interface IClientDescription {
Uri DefaultCallback { get; }
//0:有secret 1:没有secret
ClientType ClientType { get; }
//该client的secret是否为空
bool HasNonEmptySecret { get; }
//检查传入的callback与该client的callback是否一致
bool IsCallbackAllowed(Uri callback);
//检查传入的secret与该client的secret是否一致
bool IsValidClientSecret(string secret);
其中隐含了许多信息。DefaultCallback表示客户端的默认回调地址(假如有的话),在接收客户端请求时,使用IsCallbackAllowed判断回调地址是否合法(比如查看该次回调地址和默认地址是否属于同一个域),过滤其它应用的恶意请求。若ClientType 为0,则表示客户端需持密钥(secret)表明自己的身份,授权服务端可以据此赋予此类客户端相对更多的权限,因此自定义的Client类一般需要多定义一个ClientSecret属性。DefaultCallback和ClientSecret在下文常有涉及。
相关概念:,官方例子在IsValidClientSecret方法中涉及到。个人觉得此处不需考虑,因为没有为给方法单独暴露接口出来。
DotNetOpenAuth预定义了一个接口&&IAuthorizationServerHost,这是个重要的接口,定义如下:
public interface IAuthorizationServerHost
ICryptoKeyStore CryptoKeyStore { get; }
INonceStore NonceStore { get; }
AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest);
AutomatedUserAuthorizationCheckResponse CheckAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest);
AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage);
IClientDescription GetClient(string clientIdentifier);
bool IsAuthorizationValid(IAuthorizationDescription authorization);
简单地说,CryptoKeyStore用于存取对称加密密钥,用于授权码和刷新令牌的加密,由于客户端不需要对它们进行解密,所以密钥只存于授权服务端;关于AccessToken的传输则略有不同,关于这点我们待会说。理解NonceStore 属性需要知道Nonce和Timestamp的概念,Nonce与消息合并加密可防止重放攻击,Timestamp是为了避免可能的Nonce重复问题,也将一同参与加密,具体参看;这项技术放在这里主要是为了确保一个授权码只能被使用一次。CheckAuthorizeClientCredentialsGrant方法在客户端凭证模式下使用,CheckAuthorizeResourceOwnerCredentialGrant在用户名密码模式下使用,经测试,IsAuthorizationValid方法只在授权码模式下被调用(授权码换取AccessToken过程),这三个方法的返回值标示是否通过授权。
当授权通过后,通过CreateAccessToken生成AccessToken并返回给客户端,客户端于是就可以用AccessToken访问资源服务端了。那当资源服务端接收到AccessToken时,需要做什么工作呢?首先,它要确认这个AccessToken是由合法的授权服务端颁发的,否则,攻击者就能使用DotNetOpenAuth另外建一个授权服务端,生成&合法&的AccessToken,后果可想而知。说到身份认证,最成熟的就是RSA签名技术,即授权服务端私钥对AccessToken签名,资源服务端接收后使用授权服务端的公钥验证。我们还可以使用资源服务器公/私钥对来加解密AccessToken(签名在加密后),这对于OAuth2.0来说没任何意义,而是为OAuth1.0服务的(虽然https能保证传输过程加密安全性,但不保证浏览器端的安全性&&用浏览器开发者工具一看便知&&需要应用自己解决加密问题。)。
public AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage)
var accessToken = new AuthorizationServerAccessToken();
int minutes = 0;
string setting = ConfigurationManager.AppSettings["AccessTokenLifeTime"];
minutes = int.TryParse(setting, out minutes) ? minutes : 10;//10分钟
accessToken.Lifetime = TimeSpan.FromMinutes(minutes);
//这里设置加密公钥
//accessToken.ResourceServerEncryptionKey = new RSACryptoServiceProvider();
//accessToken.ResourceServerEncryptionKey.ImportParameters(ResourceServerEncryptionPublicKey);
//签名私钥,这是必须的(在后续版本中可以设置accessToken.SymmetricKeyStore替代)
accessToken.AccessTokenSigningKey = CreateRSA();
var result = new AccessTokenResult(accessToken);
前面说了,所有授权模式都是为了获取AccessToken,授权码模式和用户名密码模式还有个RefreshToken,当然授权码模式独有Authorization Code。一般来说,这三个东西,对于客户端是一个经过加密编码的字符串,对于服务端是可序列化的对象,存储相关授权信息。需要注意的是客户端证书模式没有RefreshToken,这是为什么呢?我们不妨想想为什么授权码模式和用户名密码模式有个RefreshToken,或者说RefreshToken的作用是什么。以下是我个人推测:
首先要明确,AccessToken一般是不会永久有效的。因为,AccessToken并没有承载可以验证客户端身份的完备信息,并且资源服务端也不承担验证客户端身份的职责,一旦AccessToken被他人获取,那么就有可能被恶意使用。失效机制有效减少了产生此类事故可能造成的损失。当AccessToken失效后,需要重新获取。对于授权码模式和用户名密码模式来说,假如没有RefreshToken,就意味这需要用户重新输入用户名密码进行再次授权。如果AccessToken有效期够长,比如几天,倒不觉得有何不妥,有些敏感应用只设置数分钟,就显得不够人性化了。为了解决这个问题,引入RefreshToken,它会在AccessToken失效后,在不需要用户参与的情况下,重新获取新的AccessToken,这里有个前提就是RefreshToken的有效期(如果有的话)要比AccessToken长,可设为永久有效。那么,RefreshToken泄露了会带来问题吗?答案是不会,除非你同时泄露了客户端身份凭证。需要同时具备RefreshToken和客户端凭证信息,才能获取新的AccessToken,我们甚至可以将旧的AccessToken当作RefreshToken。同理可推,由于不需要用户参与授权,在客户端证书模式下,客户端在AccessToken失效后只需提交自己的身份凭证重新请求新AccessToken即可,根本不需要RefreshToken。
授权码模式,用户授权后(此时并生成返回AccessToken,而是返回授权码),授权服务端要保存相关的授权信息,为此定义一个ClientAuthorization类:
public class ClientAuthorization
public int ClientId { get; set; }
public string UserId { get; set; }
public string Scope { get; set; }
public DateTime? ExpirationDateUtc { get; set; }
ClientId和UserId就不说了,Scope是授权范围,可以是一串Uri,也可以是其它标识,只要后台代码能通过它来判断待访问资源是否属于授权范围即可。ExpirationDateUtc乃是授权过期时间,即当该时间到期后,需要用户重新授权(有RefreshToken)也没用,为null表示永不过期。
资源服务端
在所有的授权模式下,资源服务端都只专注一件和OAuth相关的事情&&验证AccessToken。这个步骤相对来说就简单很多,以Asp.net WebAPI为例。在此之前建议对Asp.net WebAPI消息拦截机制不熟悉的朋友浏览一遍。这里我们新建一个继承自DelegatingHandler的类作为例子:
public class BearerTokenHandler : DelegatingHandler
/// &summary&
/// 验证访问令牌合法性,由授权服务器私钥签名,资源服务器通过对应的公钥验证
/// &/summary&
private static readonly RSAParameters AuthorizationServerSigningPublicKey = new RSAParameters();//just a 例子
private RSACryptoServiceProvider CreateAuthorizationServerSigningServiceProvider()
var authorizationServerSigningServiceProvider = new RSACryptoServiceProvider();
authorizationServerSigningServiceProvider.ImportParameters(AuthorizationServerSigningPublicKey);
return authorizationServerSigningServiceP
protected override Task&HttpResponseMessage& SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
if (request.Headers.Authorization != null)
if (request.Headers.Authorization.Scheme == "Bearer")
var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(this.CreateAuthorizationServerSigningServiceProvider(), null));
var principal = resourceServer.GetPrincipal(request);//可以在此传入待访问资源标识参与验证
HttpContext.Current.User =
Thread.CurrentPrincipal =
return base.SendAsync(request, cancellationToken);
需要注意,AccessToken乃是从头信息Authorization获取,格式为&Bearer:AccessToken&,在下文&&中有进一步描述(OAuth2.0引入了 Bearer 和 MAC 两种验证机制, Bearer 使用更简单,但需要 TLS, MAC 可以走 HTTP, 与 OAuth 1.0a 更接近)。ResourceServer.GetPrincipal方法使用授权服务端的公钥验证AccessToken的合法性,同时解密AccessToken,若传入参数有scope,则还会判断scope是否属于授权范围内,通过后将会话标识赋给当前会话,该会话标识乃是当初用户授权时的用户信息,这样就实现了用户信息的传递。一般来说若返回的principal为null,就可以不必执行后续逻辑了。
可以认为DotNetOpenAuth.OAuth2.Client是DotNetOpenAuth给C#客户端提供的默认SDK。我们以授权码模式为例。先声明一个IAuthorizationState接口对象,IAuthorizationState接口是用来保存最终换取AccessToken成功后授权服务端返回的信息,其部分定义如下:
public interface IAuthorizationState {
Uri Callback { get; set; }
string RefreshToken { get; set; }
string AccessToken { get; set; }
DateTime? AccessTokenIssueDateUtc { get; set; }
DateTime? AccessTokenExpirationUtc { get; set; }
HashSet&string& Scope { get; }
AccessTokenExpirationUtc是AccessToken过期时间,以Utc时间为准。若该对象为null,则表示尚未授权,我们需要去授权服务端请求。
private static AuthorizationServerDescription _authServerDescription = new AuthorizationServerDescription
TokenEndpoint = new Uri(MvcApplication.TokenEndpoint),
AuthorizationEndpoint = new Uri(MvcApplication.AuthorizationEndpoint),
private static WebServerClient _client = new WebServerClient(_authServerDescription, "democlient", "samplesecret");
[HttpPost]
public ActionResult Index()
if (Authorization == null)
return _client.PrepareRequestUserAuthorization().AsActionResult();
return View();
AuthorizationServerDescription包含两个属性,AuthorizationEndpoint是用户显式授权的地址,一般即用户输用户名密码的地;TokenEndpoint是用授权码换取AccessToken的地址,注意该地址须用POST请求。&democlient&和&samplesecret&是示例用的客户端ID和客户端Secret。WebServerClient.PrepareRequestUserAuthorization方法将会首先返回code和state到当前url,以querystring的形式(若用户授权的话)。
code即是授权码,state参数不好理解,这涉及到CSRF,可参看,state就是为了预防CSRF而引入的随机数。客户端生成该值,将其附加到state参数的同时,存入用户Cookie中,用户授权完毕后,该参数会同授权码一起返回到客户端,然后客户端将其值同Cookie中的值比较,若一样则表示该次授权为当前用户操作,视为有效。由于不同域的cookie无法共享,因此其它站点并不能知道state的确切的值,CSRF攻击也就无从谈起了。简单地说,state参数起到一个标示消息是否合法的作用。结合获取授权码这步来说,授权服务端返回的url为http://localhost:22187/?code=xxxxxxxxx&state=_PzGpfJzyQI9DkdoyWeWr格式,若忽略state,那么攻击方将code替换成自己的授权码,最终客户端获取的AccessToken是攻击方的AccessToken,由于AccessToken同用户关联,也就是说,后续客户端做的其实是另一个用户资源(也许是攻击方注册的虚拟用户),如果操作中包括新增或更新,那么录入的真实用户信息就会被攻击方获取到。现在很多客户端使用服务端的账号进行自身的登录(类似于OpenID),即账号绑定,那么攻击方即可用自己在服务端的账号管理受害者在客户端的账号信息。可参看、。
有了code就可以去换取AccessToken了:
public ActionResult Index(string code,string state)
if (!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(state))
var authorization = _client.ProcessUserAuthorization(Request);
Authorization =
return View(authorization);
return View();
如前所述,Authorization不为null即表示整个授权流程成功完成。然后就可以用它来请求资源了。
public ActionResult Invoke()
var request = new HttpRequestMessage(new HttpMethod("GET"), "/bookcates");
using (var httpClient = new HttpClient(_client.CreateAuthorizingHandler(Authorization)))
using (var resourceResponse = httpClient.SendAsync(request))
ViewBag.Result = resourceResponse.Result.Content.ReadAsStringAsync().R
return View(Authorization);
WebServerClient.CreateAuthorizingHandler方法返回一个DelegatingHandler,主要用来当AccessToken过期时,使用RefreshToken刷新换取新的AccessToken;并设置Authorization头信息,下文有进一步说明。
原生方式获取AccessToken
既然是开放平台,面对的客户端种类自然多种多样,DotNetOpenAuth.OAuth2.Client显然就不够用了,我也不打算为了这个学遍所有程序语言。所幸OAuth基于http,不管任何语言开发的客户端,获取AccessToken的步骤本质上就是提交http请求和接收http响应的过程,客户端SDK只是将这个过程封装得更易用一些。下面就让我们以授权码模式为例,一窥究竟。
参照前述事例,当我们第一次(新的浏览器会话)在客户端点击&请求授权&按钮后,会跳转到授权服务端的授权界面。
可以看到,url中带了client_id、redirect_uri、state、response_type四个参数,若要请求限定的授权范围,还可以传入scope参数。其中response_type设为code表示请求的是授权码。
以下为请求授权码:
1 private string GetNonCryptoRandomDataAsBase64(int binaryLength)
byte[] buffer = new byte[binaryLength];
_random.NextBytes(buffer);
string uniq = Convert.ToBase64String(buffer);
9 public ActionResult DemoRequestCode()
string xsrfKey = this.GetNonCryptoRandomDataAsBase64(16);//生成随机数
string url = MvcApplication.AuthorizationEndpoint + "?" +
string.Format("client_id={0}&redirect_uri={1}&response_type={2}&state={3}",
"democlient", "http://localhost:22187/", "code", xsrfKey);
HttpCookie xsrfKeyCookie = new HttpCookie(XsrfCookieName, xsrfKey);
xsrfKeyCookie.HttpOnly = true;
xsrfKeyCookie.Secure = FormsAuthentication.RequireSSL;
Response.Cookies.Add(xsrfKeyCookie);
return Redirect(url);
授权码返回后,先检查state参数,若通过则换取AccessToken:
private bool VerifyState(string state)
var cookie = Request.Cookies[XsrfCookieName];
if (cookie == null)
return false;
var xsrfCookieValue = cookie.V
return xsrfCookieValue ==
private AuthenticationHeaderValue SetAuthorizationHeader()
string concat = "democlient:samplesecret";
byte[] bits = Encoding.UTF8.GetBytes(concat);
string base64 = Convert.ToBase64String(bits);
return new AuthenticationHeaderValue("Basic", base64);
public ActionResult Demo(string code, string state)
if (!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(state) && VerifyState(state))
var httpClient = new HttpClient();
var httpContent = new FormUrlEncodedContent(new Dictionary&string, string&()
{"code", code},
{"redirect_uri", "http://localhost:22187/"},
{"grant_type","authorization_code"}
httpClient.DefaultRequestHeaders.Authorization = this.SetAuthorizationHeader();
var response = httpClient.PostAsync(MvcApplication.TokenEndpoint, httpContent).R
Authorization = response.Content.ReadAsAsync&AuthorizationState&().R
return View(Authorization);
return View();
如上所示,以Post方式提交,三个参数,code即是授权码,redirect_uri和获取授权码时传递的redirect_uri要保持一致,grant_type设置为&authorization_code&。注意SetAuthorizationHeader方法,需要设置请求头的Authorization属性,Scheme为&Basic&,Parameter为以Base64编码的&客户端ID:客户端Secret&字符串,至于为何要如此规定,暂时没有探究。成功后返回的信息可以转为前面说的IAuthorizationState接口对象。&
如前所述,当AccessToken过期后,需要用RefreshToken刷新。
private void RefreshAccessToken()
var httpClient = new HttpClient();
var httpContent = new FormUrlEncodedContent(new Dictionary&string, string&()
{"refresh_token", Authorization.RefreshToken},
{"grant_type","refresh_token"}
httpClient.DefaultRequestHeaders.Authorization = this.SetAuthorizationHeader();
var response = httpClient.PostAsync(MvcApplication.TokenEndpoint, httpContent).R
Authorization = response.Content.ReadAsAsync&AuthorizationState&().R
其中grant_type须设置为&refresh_token&,请求头信息设置同前。
获取AccessToken后,就可以用于访问用户资源了。
public ActionResult DemoInvoke()
var httpClient = new HttpClient();
if (this.Authorization.AccessTokenExpirationUtc.HasValue && this.Authorization.AccessTokenExpirationUtc.Value & DateTime.UtcNow)
this.RefreshAccessToken();
var bearerToken = this.Authorization.AccessT
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
var request = new HttpRequestMessage(new HttpMethod("GET"), "/bookcates");
using (var resourceResponse = httpClient.SendAsync(request))
ViewBag.Result = resourceResponse.Result.Content.ReadAsStringAsync().R
return View(Authorization);
用法很简单,Authorization请求头,Scheme设为&Bearer&,Parameter为AccessToken即可。
断断续续写了大半个月,到此终于可以舒一口气了。需要完整代码的朋友,我会过段时间补上。
代码链接在评论24#,有疑问可参看我的后续随笔:。
其它参考资料:
转载请注明本文出处:

我要回帖

更多关于 dotnetopenauth 下载 的文章

 

随机推荐