.NET(C#)的Twitter OAuth授权问题

我一直在拉我的头发来弄清楚我们在这里出了什么问题,显然我无法做到。 我尝试创建一个基本结构,以便在.NET(OA)上使用Twitter API(C#),但是出了点问题,我看不出它是什么。

例如,当我发送请求以获取请求令牌时 ,我返回401 Unauthorized响应,并返回如下消息:

无法validationoauth签名和令牌

我用来创建签名的签名库如下(我用虚拟值替换了我的实际使用者密钥):

POST&HTTP%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttp%3A%2F%2Flocalhost%3A44444%2Faccount%2Fauth%26oauth_consumer_key%3XXXXXXXXXXXXXXXXXXXXXXXXXXX%26oauth_nonce%3DNjM0NzkyMzk0OTk2ODEyNTAz%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1343631900%26oauth_version% 3D1.0

签名密钥只包含我的消费者密钥和一个&符号,因为我还没有令牌秘密(再次,我用虚拟值替换了我的实际消费者密钥):

签名密钥:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&

最后,我最终获得了以下Authorization标头(同样,虚拟消费者密钥):

的OAuth oauth_callback = “HTTP%3A%2F%2Flocalhost%3A44444%2Faccount%2Fauth”,oauth_consumer_key = “XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX”,oauth_nonce = “NjM0NzkyMzk0OTk2ODEyNTAz”,oauth_signature_method = “HMAC-SHA1”,oauth_timestamp = “1343631900”,oauth_version = “1.0” ,oauth_signature = “ttLvZ2Xzq4CHt%2BNM4pW7X4h1wRA%3D”

我使用的代码如下(它有点长,但我宁愿将其粘贴在此处,而不是给出一个gist URL或其他东西):

public class OAuthMessageHandler : DelegatingHandler { private const string OAuthConsumerKey = "oauth_consumer_key"; private const string OAuthNonce = "oauth_nonce"; private const string OAuthSignature = "oauth_signature"; private const string OAuthSignatureMethod = "oauth_signature_method"; private const string OAuthTimestamp = "oauth_timestamp"; private const string OAuthToken = "oauth_token"; private const string OAuthVersion = "oauth_version"; private const string OAuthCallback = "oauth_callback"; private const string HMACSHA1SignatureType = "HMAC-SHA1"; private readonly OAuthState _oAuthState; public OAuthMessageHandler(OAuthCredential oAuthCredential, OAuthSignatureEntity signatureEntity, IEnumerable<KeyValuePair> parameters, HttpMessageHandler innerHandler) : base(innerHandler) { _oAuthState = new OAuthState() { Credential = oAuthCredential, SignatureEntity = signatureEntity, Parameters = parameters, Nonce = GenerateNonce(), SignatureMethod = GetOAuthSignatureMethod(), Timestamp = GetTimestamp(), Version = GetVersion() }; } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { //add the auth header request.Headers.Authorization = new AuthenticationHeaderValue( "OAuth", GenerateAuthHeader(_oAuthState, request) ); return base.SendAsync(request, cancellationToken); } private string GetTimestamp() { TimeSpan timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); return Convert.ToInt64(timeSpan.TotalSeconds).ToString(); } private string GetVersion() { return "1.0"; } private string GetOAuthSignatureMethod() { return HMACSHA1SignatureType; } private string GenerateNonce() { return Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString())); } private string GenerateSignature(OAuthState oAuthState, HttpRequestMessage request) { //https://dev.twitter.com/docs/auth/creating-signature //http://garyshortblog.wordpress.com/2011/02/11/a-twitter-oauth-example-in-c/ SortedDictionary signatureCollection = new SortedDictionary(); //Required for all requests signatureCollection.Add(OAuthConsumerKey, oAuthState.Credential.ConsumerKey); signatureCollection.Add(OAuthNonce, oAuthState.Nonce); signatureCollection.Add(OAuthVersion, oAuthState.Version); signatureCollection.Add(OAuthTimestamp, oAuthState.Timestamp); signatureCollection.Add(OAuthSignatureMethod, oAuthState.SignatureMethod); //Parameters if (oAuthState.Parameters != null) { oAuthState.Parameters.ForEach(x => signatureCollection.Add(x.Key, x.Value)); } //Optionals if (!string.IsNullOrEmpty(oAuthState.Credential.Token)) signatureCollection.Add(OAuthToken, oAuthState.Credential.Token); if (!string.IsNullOrEmpty(oAuthState.Credential.CallbackUrl)) signatureCollection.Add(OAuthCallback, oAuthState.Credential.CallbackUrl); //Build the signature StringBuilder strBuilder = new StringBuilder(); strBuilder.AppendFormat("{0}&", request.Method.Method.ToUpper()); strBuilder.AppendFormat("{0}&", Uri.EscapeDataString(request.RequestUri.ToString())); signatureCollection.ForEach(x => strBuilder.Append( Uri.EscapeDataString(string.Format("{0}={1}&", x.Key, x.Value)) ) ); //Remove the trailing ambersand char from the signatureBase. //Remember, it's been urlEncoded so you have to remove the //last 3 chars - %26 string baseSignatureString = strBuilder.ToString(); baseSignatureString = baseSignatureString.Substring(0, baseSignatureString.Length - 3); //Build the signing key string signingKey = string.Format( "{0}&{1}", Uri.EscapeDataString(oAuthState.SignatureEntity.ConsumerSecret), string.IsNullOrEmpty(oAuthState.SignatureEntity.OAuthTokenSecret) ? "" : Uri.EscapeDataString(oAuthState.SignatureEntity.OAuthTokenSecret) ); //Sign the request using (HMACSHA1 hashAlgorithm = new HMACSHA1(new ASCIIEncoding().GetBytes(signingKey))) { return Convert.ToBase64String( hashAlgorithm.ComputeHash( new ASCIIEncoding().GetBytes(baseSignatureString) ) ); } } private string GenerateAuthHeader(OAuthState oAuthState, HttpRequestMessage request) { SortedDictionary sortedDictionary = new SortedDictionary(); sortedDictionary.Add(OAuthNonce, Uri.EscapeDataString(oAuthState.Nonce)); sortedDictionary.Add(OAuthSignatureMethod, Uri.EscapeDataString(oAuthState.SignatureMethod)); sortedDictionary.Add(OAuthTimestamp, Uri.EscapeDataString(oAuthState.Timestamp)); sortedDictionary.Add(OAuthConsumerKey, Uri.EscapeDataString(oAuthState.Credential.ConsumerKey)); sortedDictionary.Add(OAuthVersion, Uri.EscapeDataString(oAuthState.Version)); if (!string.IsNullOrEmpty(_oAuthState.Credential.Token)) sortedDictionary.Add(OAuthToken, Uri.EscapeDataString(oAuthState.Credential.Token)); if (!string.IsNullOrEmpty(_oAuthState.Credential.CallbackUrl)) sortedDictionary.Add(OAuthCallback, Uri.EscapeDataString(oAuthState.Credential.CallbackUrl)); StringBuilder strBuilder = new StringBuilder(); var valueFormat = "{0}=\"{1}\","; sortedDictionary.ForEach(x => { strBuilder.AppendFormat(valueFormat, x.Key, x.Value); }); //oAuth parameters has to be sorted before sending, but signature has to be at the end of the authorization request //http://stackoverflow.com/questions/5591240/acquire-twitter-request-token-failed strBuilder.AppendFormat(valueFormat, OAuthSignature, Uri.EscapeDataString(GenerateSignature(oAuthState, request))); return strBuilder.ToString().TrimEnd(','); } private class OAuthState { public OAuthCredential Credential { get; set; } public OAuthSignatureEntity SignatureEntity { get; set; } public IEnumerable<KeyValuePair> Parameters { get; set; } public string Nonce { get; set; } public string Timestamp { get; set; } public string Version { get; set; } public string SignatureMethod { get; set; } } } 

这里有一个新的.NET HttpClient混合,但授权标题生成代码很清楚。

那么,这里的问题是什么?我错过了什么?

编辑:

我尝试了不同的端点(例如/1/account/update_profile.json),它在我发送身体不需要编码的请求时起作用。 例如: location=Marmaris有效,但location=Marmaris, Turkey即使我使用Uri.EscapeDataString编码它location=Marmaris, Turkey也无法工作。

编辑:

我尝试使用Twitter OAuth工具来查看我的签名库和twitter之间是否有任何特别的区别,我可以看到twitter的编码与我的不同。 例如,Twitter为location=Marmaris, Turkey生成location%3DMarmaris%252C%2520Turkeylocation=Marmaris, Turkey但我生产的是location%3DMarmaris%2C%20Turkey

似乎所有问题都与编码问题有关,而现在看来我解决了这个问题。 以下是有关该问题的一些信息:

当我们创建签名库时,参数值需要编码两次。 例如,我有一个如下集合:

 var collection = new List>(); collection.Add(new KeyValuePair("location", Uri.EscapeDataString(locationVal))); collection.Add(new KeyValuePair("url", Uri.EscapeDataString(profileUrl))); 

当我将它传递给GenerateSignature方法时,该方法再编码这一次,这导致百分号被编码为%25 ,这使它工作。 我的一个问题已经解决,但我仍然无法在那时成功发出请求令牌请求。

然后,我查看了“request_token”请求并看到了以下代码行:

 OAuthCredential creds = new OAuthCredential(_consumerKey) { CallbackUrl = "http://localhost:44444/account/auth" }; 

我正在发送CallbackUrl以便对其进行编码,但由于twitter需要对签名库进行两次编码,我认为这可能是问题所在。 然后,我用以下代码替换了这段代码:

 OAuthCredential creds = new OAuthCredential(_consumerKey) { CallbackUrl = Uri.EscapeDataString("http://localhost:44444/account/auth") }; 

我做的另一个更改是在GenerateAuthHeader方法中,因为我不需要对CallbackUrl两次编码。

 //don't encode it here again. //we already did that and auth header doesn't require it to be encoded twice if (!string.IsNullOrEmpty(_oAuthState.Credential.CallbackUrl)) sortedDictionary.Add(OAuthCallback, oAuthState.Credential.CallbackUrl); 

而且我没有遇到任何问题。

Tugberk在这里是另一个OAuth lib,这里没有魔法。

http://oauth.googlecode.com/svn/code/csharp/OAuthBase.cs