.NET中具有验证功能的OAuth


103

我正在尝试创建一个基于.NET的客户端应用程序(在WPF中-尽管目前我只是作为控制台应用程序来做),以与支持OAuth的应用程序(特别是Mendeley(http:// dev (.mendeley.com),显然使用了三足式OAuth。

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

到目前为止,我已经尝试过:

第一个(DotNetOpenAuth)似乎可以完成我需要的工作,如果我花了数小时来尝试如何做。据我所知,第二和第三个不支持Mendeley发送回的验证码-尽管我对此可能有误:)

我从Mendeley获得了一个用户密钥和机密,并且使用DotNetOpenAuth设法启动了一个带有Mendeley页面的浏览器,该页面提供了供用户输入应用程序的验证码。但是,这时我迷路了,无法解决如何明智地将其提供回应用程序的问题。

我非常愿意承认,我不知道从哪里开始(尽管似乎有一个陡峭的学习曲线)-如果有人可以指出正确的方向,我将不胜感激!

Answers:


182

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

不需要这么复杂。

我不是OAuth专家,但是我制作了一个OAuth客户端管理器类,该类已成功用于Twitter和TwitPic。使用起来比较简单。它是开源的,可以在这里找到:Oauth.cs

为了进行审查,在OAuth 1.0a ...有点有趣的情况下,有一个特殊的名称,它看起来像是“标准”,但据我所知,唯一实现“ OAuth 1.0a”的服务是Twitter。我想这已经足够标准。好的,无论如何,在OAuth 1.0a中,它对桌面应用的工作方式是这样的:

  1. 您,该应用程序的开发者,注册该应用程序并获得“消费者密钥”和“消费者秘密”。在Arstechnica,还有为什么这种模式是不是最好的一个写得很好的分析,但他们说,这是它是什么

  2. 您的应用运行。首次运行时,它需要使用户明确批准该应用程序的批准,以便向Twitter及其姊妹服务(例如TwitPic)发出经oauth认证的REST请求。为此,您必须经过批准过程,涉及用户的明确批准。这仅在应用程序首次运行时发生。像这样:

    • 请求一个“请求令牌”。又名临时令牌。
    • 弹出网页,将该请求令牌作为查询参数传递。该网页向用户显示UI,询问“您是否要授予对此应用程序的访问权限?”
    • 用户登录到Twitter网页,并授予或拒绝访问。
    • 出现响应html页面。如果用户已授予访问权限,则会以48点字体显示PIN
    • 用户现在需要将图钉剪切/粘贴到Windows窗体框中,然后单击“下一步”或类似内容。
    • 然后,桌面应用程序会对“访问令牌”进行经过oauth身份验证的请求。另一个REST请求。
    • 桌面应用会收到“访问令牌”和“访问密码”。

在批准舞蹈之后,桌面应用程序可以仅使用用户特定的“访问令牌”和“访问秘密”(以及应用程序特定的“消费者密钥”和“消费者秘密”)来代表用户执行经过身份验证的请求到Twitter。这些不会过期,尽管如果用户取消了对应用程序的授权,或者如果Twitter由于某种原因取消了对应用程序的授权,或者如果您丢失了访问令牌和/或机密,则需要再次进行批准操作。


如果您不聪明,则UI流程可以类似于多步OAuth消息流程的镜像。有个更好的方法。

使用WebBrowser控件,然后在桌面应用程序中打开授权网页。当用户单击“允许”时,从该WebBrowser控件中获取响应文本,自动提取PIN,然后获取访问令牌。您发送5或6个HTTP请求,但用户只需要看到一个“允许/拒绝”对话框。简单。

像这样:
替代文字


如果您已经对UI进行了排序,那么剩下的唯一挑战就是生成oauth签名的请求。因为oauth签名要求有些特殊,所以这使很多人绊倒了。这就是简化的OAuth Manager类的作用。

请求令牌的示例代码:

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方法将oauth签名的请求发送到服务的URL,该URL授予请求令牌(也称为临时令牌)。对于Twitter,此URL为“ https://api.twitter.com/oauth/request_token ”。oauth规范表示,您需要以某种方式(URL编码并由&符组合)并以字典上的方式打包oauth参数集(令牌,token_secret,随机数,时间戳,consumer_key,版本和回调)。排序后,在结果上生成签名,然后将这些相同的参数与签名一起打包,并以不同的方式(存储在新的oauth_signature参数中)(以逗号分隔)。 OAuth管理器类会自动为您执行此操作。 它会自动生成随机数,时间戳,版本和签名 -您的应用无需关心或注意这些内容。只需设置oauth参数值并进行简单的方法调用即可。manager类发出请求并为您解析响应。

好那怎么了 获得请求令牌后,将弹出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控件的DocumentedCompleted事件注册一个处理程序,然后在该处理程序中抓图钉:

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屏幕抓取。

抓住图钉之后,您就不再需要网络浏览器,因此:

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

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

下一步是通过与该引脚一起发送另一个HTTP消息来获取访问令牌。这是另一个已签名的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和realm的值分别为“ 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。该下载中也有一个帮助文件。或者,您可以在线查看帮助文件

在此处查看使用此管理器的Windows窗体示例。


工作实例

下载使用此处描述的类和技术的命令行工具的工作示例


嗨,非常感谢您的回复!我实际上已经从OAuth转移了(我放弃了Mendeley并选择了其他方法)-但是我仔细阅读了您的回答,它很有意义并且非常全面。我也为您将来可能需要的所有课程添加了书签!再次非常感谢。
约翰2010年

2
嗨,Cheeso,谢谢您分享代码和详细说明。您提供了一个很棒而简单的解决方案。但是,您将需要对GetSignatureBase方法进行一些小的更改以支持非“ oob”解决方案。对于非“ oob”,您需要对回调进行URL编码,因此在遍历this._params时,您需要添加类似的内容。if(p1.Key ==“ callback”){p.Add( “ oauth_” + p1.Key,UrlEncode(p1.Value));继续;}
约翰尼·

1
这不适用于OAuth 2.0。此类适用于OAuth 1.0a。由于没有对各种参数进行签名和字典排序,因此OAuth2.0的使用非常简单。因此,您可能不需要外部类来执行OAuth 2.0,或者...如果您需要外部类,它将比这个简单得多。
Cheeso

1
找不到在线帮助文​​件 cheeso.members.winisp.net/OAuthManager1.1
Kiquenet

3
所有链接似乎都断开了。我在这里找到了一个副本:gist.github.com/DeskSupport/2951522#file-oauth-cs
约翰·
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.