.NET中的OAuth验证

110

我正在尝试创建一个基于.NET的客户端应用程序(在WPF中 - 尽管目前只是作为控制台应用程序)来与OAuth启用的应用程序集成,特别是Mendeley(http://dev.mendeley.com),它显然使用3-legged OAuth。

这是我第一次使用OAuth,我在开始时遇到了很多困难。我找到了几个.NET OAuth库或助手,但它们似乎比我需要的要复杂。我想做的就是能够向Mendeley API发出REST请求并获得响应!

到目前为止,我尝试过:

第一个(DotNetOpenAuth)似乎可能可以做到我所需求的,如果我花费数小时去尝试。第二个和第三个,据我所知,不支持Mendeley发送回来的验证代码 - 尽管我可能会错 :)

我已经从Mendeley获得了消费者密钥和密钥,使用DotNetOpenAuth,我设法启动了一个浏览器,并在Mendeley页面上提供了一个验证代码供用户输入到应用程序中。但是,在这一点上,我迷失了方向,无法理性地将它提供回应用程序。

我非常愿意承认我对此毫不了解(尽管似乎有相当陡峭的学习曲线) - 如果有人能指点我正确的方向,我会很感激!

1个回答

189

我同意你的看法。对于.NET应用程序可用的开源OAuth支持类很难理解,过于复杂(DotNetOpenAuth公开了多少方法?),设计不佳(查看您提供的Google链接中的OAuthBase.cs模块中具有10个字符串参数的方法 - 根本没有状态管理),或者其他方面不尽人意。

这并不需要这么复杂。

我不是OAuth专家,但我已经编写了一个OAuth客户端管理器类,我已经成功地与Twitter和TwitPic一起使用。它相对简单易用。它是开源的,并在此处提供:Oauth.cs

回顾一下,在OAuth 1.0a中...有点有趣,它有一个特殊的名称,看起来像一个“标准”,但据我所知,唯一实现“OAuth 1.0a”的服务是Twitter。 我想那就够标准了。 好吧,无论如何,在OAuth 1.0a中,它适用于桌面应用程序的方式是:

  1. 你作为应用程序的开发者,需要注册应用并获得"consumer key"和"consumer secret"。Arstechnica网站上有一篇关于为什么这种模式不是最好的分析,但正如他们所说,"它就是它"。

  2. 你的应用程序运行。第一次运行时,它需要让用户明确授权,以便应用程序可以通过oauth身份验证向Twitter及其姐妹服务(如TwitPic)进行REST请求。为此,您必须经过一个批准流程,包括用户的明确批准。这只在应用程序第一次运行时发生。流程如下:

    • 请求一个"request token",也称为临时令牌。
    • 弹出一个Web页面,将该请求令牌作为查询参数传递。该Web页面向用户呈现UI,询问"您是否要授权此应用程序访问?"
    • 用户登录Twitter网页,并授予或拒绝访问权限。
    • 响应的HTML页面出现。如果用户已授予访问权限,则会显示一个48号字体的PIN。
    • 用户现在需要将那个PIN复制/粘贴到Windows表单框中,然后点击"下一步"或类似的按钮。
    • 桌面应用程序然后执行oauth身份验证请求,以获取"Access token"。另一个REST请求。
    • 桌面应用程序接收"access token"和"access secret"。

在认证通过后,桌面应用程序只需使用用户特定的"访问令牌"和"访问密钥"(以及应用程序特定的"消费者密钥"和"消费者密钥")代表用户向Twitter进行经过身份验证的请求。这些不会过期,但如果用户取消授权应用程序,或者Twitter出于某种原因取消了您的应用程序的授权,或者您丢失了访问令牌和/或秘密,您需要再次进行认证。


如果你不够聪明,UI流程会有点像多步骤OAuth消息流。 有更好的方式。
使用WebBrowser控件,在桌面应用程序中打开授权网页。 当用户点击“允许”时,从WebBrowser控件中获取响应文本,自动提取PIN,然后获取访问令牌。 您发送5或6个HTTP请求,但用户只需要看到一个Allow / Deny对话框。 简单。
就像这样:
alt text
如果你已经解决了UI问题,唯一需要解决的挑战就是生成OAuth签名请求。这会让很多人感到困惑,因为OAuth签名要求比较特殊。这就是简化的OAuth管理器类所做的事情。
请求令牌的示例代码:
var oauth = new OAuth.Manager();
// the URL to obtain a temporary "request token"
var rtUrl = "https://api.twitter.com/oauth/request_token";
oauth["consumer_key"] = MY_APP_SPECIFIC_KEY;
oauth["consumer_secret"] = MY_APP_SPECIFIC_SECRET;    
oauth.AcquireRequestToken(rtUrl, "POST");

就是这样。简单吧。从代码中可以看出,获取oauth参数的方法是通过基于字符串的索引器,类似于字典。AcquireRequestToken方法向授予临时令牌的服务的URL发送oauth签名请求。对于Twitter来说,这个URL是 "https://api.twitter.com/oauth/request_token"。oauth规范要求以一定的方式(url编码并用&连接)打包oauth参数集合(token、token_secret、nonce、timestamp、consumer_key、version和callback),并按字典顺序排序,生成签名,然后将这些相同的参数与签名一起打包,存储在新的oauth_signature参数中,并以不同的方式(逗号分隔)打包。OAuth管理器类会自动为您完成此操作。它会自动生成随机数、时间戳、版本和签名 - 您的应用程序不需要关心或知道这些内容。只需设置oauth参数值并进行简单的方法调用即可,管理器类会为您发送请求并解析响应。

好了,那接下来呢?获得请求令牌后,您需要弹出Web浏览器UI,让用户明确批准。如果您做得正确,您将在嵌入式浏览器中弹出它。对于Twitter来说,这个URL是 "https://api.twitter.com/oauth/authorize?oauth_token=",其中附加了oauth_token。可以通过以下代码实现:

var url = SERVICE_SPECIFIC_AUTHORIZE_URL_STUB + oauth["token"];
webBrowser1.Url = new Uri(url);

(如果您在外部浏览器中执行此操作,则会使用System.Diagnostics.Process.Start(url)。)

设置Url属性会导致WebBrowser控件自动导航到该页面。

当用户单击“允许”按钮时,将加载一个新页面。它是一个HTML表单,并且与完整浏览器中的操作方式相同。在您的代码中,为WebBrowser控件的DocumentCompleted事件注册处理程序,在该处理程序中获取PIN:

var divMarker = "<div id=\"oauth_pin\">"; // the div for twitter's oauth pin
var index = webBrowser1.DocumentText.LastIndexOf(divMarker) + divMarker.Length;
var snip = web1.DocumentText.Substring(index);
var pin = RE.Regex.Replace(snip,"(?s)[^0-9]*([0-9]+).*", "$1").Trim();

这是一些与HTML屏幕抓取相关的内容。

获取了PIN码后,您就不再需要Web浏览器了,因此:

webBrowser1.Visible = false; // all done with the web UI

...并且您可能还想调用Dispose()。

下一步是通过发送另一个HTTP消息来获取访问令牌,并附带该pin。这是另一个签名的oauth调用,使用我上面描述的oauth排序和格式构建。但再次使用OAuth.Manager类真的很简单:

oauth.AcquireAccessToken(URL_ACCESS_TOKEN,
                         "POST",
                         pin);

Twitter的URL为"https://api.twitter.com/oauth/access_token"。

现在您拥有访问令牌,可以在签名的HTTP请求中使用它们。像这样:

var authzHeader = oauth.GenerateAuthzHeader(url, "POST");

...其中url是资源终端点。要更新用户的状态,可以使用"http://api.twitter.com/1/statuses/update.xml?status=Hello"。

然后将该字符串设置为名为Authorization的HTTP标头。

要与TwitPic等第三方服务进行交互,您需要构建一个稍微不同的OAuth标头,如下所示:

var authzHeader = oauth.GenerateCredsHeader(URL_VERIFY_CREDS,
                                            "GET",
                                            AUTHENTICATION_REALM);

对于Twitter,验证凭证URL和领域的值分别为“https://api.twitter.com/1/account/verify_credentials.json”和“http://api.twitter.com/”。将该授权字符串放入名为X-Verify-Credentials-Authorization的HTTP标头中,并将其与您发送的任何请求一起发送到您的服务,例如TwitPic。这就是全部。总体而言,更新Twitter状态的代码可能如下所示:
// the URL to obtain a temporary "request token"
var rtUrl = "https://api.twitter.com/oauth/request_token";
var oauth = new OAuth.Manager();
// The consumer_{key,secret} are obtained via registration
oauth["consumer_key"] = "~~~CONSUMER_KEY~~~~";
oauth["consumer_secret"] = "~~~CONSUMER_SECRET~~~";
oauth.AcquireRequestToken(rtUrl, "POST");
var authzUrl = "https://api.twitter.com/oauth/authorize?oauth_token=" + oauth["token"];
// here, should use a WebBrowser control. 
System.Diagnostics.Process.Start(authzUrl);  // example only!
// instruct the user to type in the PIN from that browser window
var pin = "...";
var atUrl = "https://api.twitter.com/oauth/access_token";
oauth.AcquireAccessToken(atUrl, "POST", pin);

// now, update twitter status using that access token
var appUrl = "http://api.twitter.com/1/statuses/update.xml?status=Hello";
var authzHeader = oauth.GenerateAuthzHeader(appUrl, "POST");
var request = (HttpWebRequest)WebRequest.Create(appUrl);
request.Method = "POST";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.Headers.Add("Authorization", authzHeader);

using (var response = (HttpWebResponse)request.GetResponse())
{
    if (response.StatusCode != HttpStatusCode.OK)
        MessageBox.Show("There's been a problem trying to tweet:" +
                        Environment.NewLine +
                        response.StatusDescription);
}

OAuth 1.0a在底层有点复杂,但使用它并不需要太多的复杂操作。 OAuth.Manager负责生成外部oauth请求,并接收和处理响应中的oauth内容。当Request_token请求给你一个oauth_token时,你的应用程序不需要存储它。Oauth.Manager足够聪明,可以自动完成这个过程。同样地,当access_token请求返回访问令牌和密钥时,您不需要显式存储它们。OAuth.Manager会为您处理这个状态。

在后续运行中,当您已经拥有访问令牌和密钥时,您可以像这样实例化OAuth.Manager:

var oauth = new OAuth.Manager();
oauth["consumer_key"] = CONSUMER_KEY;
oauth["consumer_secret"] = CONSUMER_SECRET;
oauth["token"] = your_stored_access_token;
oauth["token_secret"] = your_stored_access_secret;

...然后按照上述方法生成授权头。

// now, update twitter status using that access token
var appUrl = "http://api.twitter.com/1/statuses/update.xml?status=Hello";
var authzHeader = oauth.GenerateAuthzHeader(appUrl, "POST");
var request = (HttpWebRequest)WebRequest.Create(appUrl);
request.Method = "POST";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.Headers.Add("Authorization", authzHeader);

using (var response = (HttpWebResponse)request.GetResponse())
{
    if (response.StatusCode != HttpStatusCode.OK)
        MessageBox.Show("There's been a problem trying to tweet:" +
                        Environment.NewLine +
                        response.StatusDescription);
}

您可以在此处下载包含OAuth.Manager类的DLL文件a DLL containing the OAuth.Manager class here。该下载中还有一个帮助文件。或者,您可以在线查看帮助文件

在此处here查看使用此管理器的Windows表单示例。


实际工作示例

下载一个实际工作示例,其中包含使用本文所述的类和技术的命令行工具:


2
嗨Cheeso,感谢您分享您的代码和详细的解释。您提供了一个简单而又伟大的解决方案。然而,在GetSignatureBase方法中,您需要进行一些小的更改以支持非“oob”的解决方案。对于非“oob”,您需要对回调进行URL编码,因此在遍历this.params时,您需要添加类似以下内容的内容:if (p1.Key == "callback") {p.Add("oauth" + p1.Key, UrlEncode(p1.Value));continue;} - Johnny Oshika
1
这对OAuth 2.0不起作用。这个类是为OAuth 1.0a设计的。OAuth 2.0使用起来要简单得多,因为没有各种参数的签名和词典排序。所以你可能不需要一个外部类来处理OAuth 2.0,或者...如果你确实需要一个外部类,它会比这个简单得多。 - Cheeso
1
未找到在线帮助文件:http://cheeso.members.winisp.net/OAuthManager1.1/ - Kiquenet
4
所有的链接似乎都已经失效了。我在这里找到了一份备份:https://gist.github.com/DeskSupport/2951522#file-oauth-cs - John
1
这是与OAuth.cs文件相关的另一个镜像,因为原始链接已失效。请查看以下内容:https://pastebin.com/eQ0gtTtM - Jon Story
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接