我一直在拉我的头发来弄清楚我们在这里出了什么问题,显然我无法做到。我尝试创建一个基本结构,以便在.NET上使用OA API(C#),但出了点问题,我看不出是什么。
例如,当我发送request in order to obtain a request token时,我会收到401 Unauthorized响应,并返回如下消息:
无法验证oauth签名和令牌
我用来创建签名的签名库如下(我用虚拟值替换了我的实际使用者密钥):
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
签名密钥只包含我的消费者密钥和一个&符号,因为我还没有令牌秘密(再次,我用虚拟值替换了我的实际消费者密钥):
signingKey:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&
最后,我最终获得了以下授权标题(再次,虚拟消费者密钥):
的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<string, string>> 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<HttpResponseMessage> 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<string, string> signatureCollection = new SortedDictionary<string, string>();
//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<string, string> sortedDictionary = new SortedDictionary<string, string>();
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<string, string>> 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
对其进行编码也不起作用。
修改
我尝试使用Twitter OAuth工具来查看我的签名库和twitter之间是否有任何特别的区别,我可以看到twitter的编码与我的不同。例如,Twitter为location%3DMarmaris%252C%2520Turkey
生成location=Marmaris, Turkey
值,但我生成的是location%3DMarmaris%2C%20Turkey
。
答案 0 :(得分:0)
似乎所有问题都与编码问题有关,而现在看来我解决了这个问题。以下是有关该问题的一些信息:
当我们创建签名库时,参数值需要编码两次。例如,我有一个如下集合:
var collection = new List<KeyValuePair<string, string>>();
collection.Add(new KeyValuePair<string, string>("location", Uri.EscapeDataString(locationVal)));
collection.Add(new KeyValuePair<string, string>("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);
我没有遇到任何问题。
答案 1 :(得分:0)
这里的Tugberk是另一个OAuth lib,这里没有魔法。