Twitter OAuth请求令牌返回未经授权状态。

3
我正试图从Twitter OAuth获取请求令牌,但我一直收到“401未经授权”的响应,但我不知道原因。我已尽力按照签名创建的过程进行操作,并且曾经遇到过“400错误请求”问题,最终成功解决它并得到了有效请求,但现在面对的却是401错误。
我感觉自己可能错过了非常简单的东西,但我就是找不出来。
这是我正在使用的代码:
using (HttpClient client = new HttpClient())
{
    client.BaseAddress = new Uri("https://api.twitter.com/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

    string oauth_nonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));
    string oauth_callback = Uri.EscapeUriString("oob");
    string oauth_signature_method = "HMAC-SHA1";
    string oauth_timestamp = Convert.ToInt64(ts.TotalSeconds).ToString();
    string oauth_consumer_key = OAUTH_KEY;
    string oauth_version = "1.0";

    // Generate signature
    string baseString = "POST&";
    baseString += Uri.EscapeDataString(client.BaseAddress + "oauth/request_token") + "&";
    // Each oauth value sorted alphabetically
    baseString += Uri.EscapeDataString("oauth_callback=" + oauth_callback + "&");
    baseString += Uri.EscapeDataString("oauth_consumer_key=" + oauth_consumer_key + "&");
    baseString += Uri.EscapeDataString("oauth_nonce=" + oauth_nonce + "&");
    baseString += Uri.EscapeDataString("oauth_signature_method=" + oauth_signature_method + "&");
    baseString += Uri.EscapeDataString("oauth_timestamp=" + oauth_timestamp + "&");
    baseString += Uri.EscapeDataString("oauth_version=" + oauth_version + "&");

    string signingKey = Uri.EscapeDataString(OAUTH_SECRET) + "&";
    HMACSHA1 hasher = new HMACSHA1(new ASCIIEncoding().GetBytes(signingKey));
    string oauth_signature = Convert.ToBase64String(hasher.ComputeHash(new ASCIIEncoding().GetBytes(baseString)));

    client.DefaultRequestHeaders.Add("Authorization", "OAuth " +
        "oauth_nonce=\"" + oauth_nonce + "\"," +
        "oauth_callback=\"" + oauth_callback + "\"," +
        "oauth_signature_method=\"" + oauth_signature_method + "\"," +
        "oauth_timestamp=\"" + oauth_timestamp + "\"," +
        "oauth_consumer_key=\"" + oauth_consumer_key + "\"," +
        "oauth_signature=\"" + Uri.EscapeDataString(oauth_signature) + "\"," +
        "oauth_version=\"" + oauth_version + "\""
    );

    HttpResponseMessage response = await client.PostAsJsonAsync("oauth/request_token", "");
    var responseString = await response.Content.ReadAsStringAsync();
}

我正在使用 .Net 4.5,所以 Uri.EscapeDataString() 应该能给出正确的百分号编码字符串。
编辑:
我注意到 oauth_nonce 值有时包含非字母数字字符,因此我将变量计算更改为以下内容:
string nonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));
string oauth_nonce = new string(nonce.Where(c => Char.IsLetter(c) || Char.IsDigit(c)).ToArray());

很遗憾,这并没有帮助。

我还尝试过对Authorization头中的所有值使用Uri.EscapeUriData(),以及在每个逗号后面添加一个空格(因为Twitter的文档是这样说的),但这也没有起作用。


尝试更改此行以使用 DateTime.UtcNowstring oauth_nonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.UtcNow.Ticks.ToString())); - Martin Costello
抱歉,忽略我的回复,我没有正确阅读它的使用方式。 - Martin Costello
在URL中添加空格是一个不好的主意,比如oauth/request_token - Patrick Hofman
@PatrickHofman 很抱歉,复制粘贴出了错。 - GTHvidsten
2个回答

4
我从零开始尝试了一种稍微不同的方法,但应该产生相同的结果。然而,它并没有产生相应的结果,反而按照预期工作了!
如果有人能指出这段代码与原问题中的代码之间的实际差异,我将不胜感激,因为在我看来,它们产生了相同的结果。
using (HttpClient client = new HttpClient())
{
    client.BaseAddress = new Uri("https://api.twitter.com/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    TimeSpan timestamp = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
    string nonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString()));

    Dictionary<string, string> oauth = new Dictionary<string, string>
    {
        ["oauth_nonce"] = new string(nonce.Where(c => char.IsLetter(c) || char.IsDigit(c)).ToArray()),
        ["oauth_callback"] = "http://my.callback.url",
        ["oauth_signature_method"] = "HMAC-SHA1",
        ["oauth_timestamp"] = Convert.ToInt64(timestamp.TotalSeconds).ToString(),
        ["oauth_consumer_key"] = OAUTH_KEY,
        ["oauth_version"] = "1.0"
    };

    string[] parameterCollectionValues = oauth.Select(parameter =>
            Uri.EscapeDataString(parameter.Key) + "=" +
            Uri.EscapeDataString(parameter.Value))
        .OrderBy(kv => kv)
        .ToArray();
    string parameterCollection = string.Join("&", parameterCollectionValues);

    string baseString = "POST";
    baseString += "&";
    baseString += Uri.EscapeDataString(client.BaseAddress + "oauth/request_token");
    baseString += "&";
    baseString += Uri.EscapeDataString(parameterCollection);

    string signingKey = Uri.EscapeDataString(OAUTH_SECRET);
    signingKey += "&";
    HMACSHA1 hasher = new HMACSHA1(new ASCIIEncoding().GetBytes(signingKey));
    oauth["oauth_signature"] = Convert.ToBase64String(hasher.ComputeHash(new ASCIIEncoding().GetBytes(baseString)));

    string headerString = "OAuth ";
    string[] headerStringValues = oauth.Select(parameter =>
            Uri.EscapeDataString(parameter.Key) + "=" + "\"" +
            Uri.EscapeDataString(parameter.Value) + "\"")
        .ToArray();
    headerString += string.Join(", ", headerStringValues);

    client.DefaultRequestHeaders.Add("Authorization", headerString);

    HttpResponseMessage response = await client.PostAsJsonAsync("oauth/request_token", "");
    var responseString = await response.Content.ReadAsStringAsync();
}

这个完全可行。感谢您的发布。我自己在处理这个问题时很烦恼。每次调用Twitter API似乎格式都有点不同,这真的让人抓狂。他们的示例并没有提供太多帮助。 - Don Rolling
你的示例确实有效,谢谢。回顾Twitter文档中有关创建签名的部分,他们忽略了提到某些请求(例如对request_token的调用)需要在参数字符串中包含oauth_callback="some address",除了其他oauth参数之外。添加oauth_callback参数解决了我的问题。 - James Blake
非常感谢。我为此苦苦挣扎了很久! - Bad Dub
我特意登录SO,只是为了告诉你你太棒了!虽然这是一个老问题,但它解决了我的问题,尤其是在我苦思冥想了三天之后。OAuth v2现在已经发布,我设法在几个小时内得到了一个完整的工作解决方案,但v2没有上传媒体的端点!我用v1重写了代码,遇到了很多问题!感谢你挽救了我的头发线。我当时非常紧张。 - Tez Wingfield

0

感谢 @GTHvidsten 提供的示例。

通过研究您的第二个工作示例,我发现了我的代码问题,并且很可能是您的原始示例无法正常工作的原因。

仅对构成签名基本字符串的单个请求参数键/值对进行编码是不够的,显然还必须对整个连接的键/值请求参数字符串进行编码,从而编码“=”符号。在您的第二个示例中,这行代码就是这样做的:baseString += Uri.EscapeDataString(parameterCollection);

在您的原始示例中,没有对请求参数进行编码的代码。


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