1

为了让 BitBucket OAuth API 正常工作,我已经把头撞在桌子上大约一整周了,但我什至无法获得一个请求令牌来挽救我自己的生命。我在此处编写的代码适用于 LinkedIn,但 BitBucket 始终返回 HTTP 状态代码 400,并带有非常详细且有用的消息:“无法验证 OAuth 请求”。

这是一个简单的代码(50 行,不能让它更短),之所以这样,是因为我需要手动实现 OAuth,否则我不会在这里询问并使用其他外部库,但由于公司的要求,我不是允许在此项目中使用外部库。不过我看不出有什么问题,1.0a 并不难,获取请求令牌不应该花这么长时间。有什么问题?

我还检查了我的时间戳,这很好,针对 pool.ntp.org 的 w32tm.exe 以 +30 或其他值返回时间。我还尝试在 UtcNow 时间戳中添加和删除 30 分钟,但没有成功,但我的时钟已正确同步(与本地时间和正确的 GMT 值 (GMT -4:30)),因此根本没有任何意义。

可能是因为我位于公司防火墙 (Forefront) 后面吗?但是,为什么 LinkedIn 的 API 调用有效而 BitBucket 无效?我还阅读了很多文档,例如 OAuth 圣经、RFC、官方文档等。当然,在询问之前对 SO 进行了广泛搜索,并在点击之前查看了“类似问题”面板中显示的所有链接“发布您的问题”按钮。

这是简单的代码(C#):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Security.Cryptography;

namespace OAuthTest1
{
    class Program
    {
        static void Main(string[] args)
        {
            String key = "KEY";
            String secret = "SECRET";
            String requestUrl = "https://bitbucket.org/api/1.0/oauth/request_token";

            string sigBaseStringParams = "oauth_consumer_key=" + key;
            sigBaseStringParams += "&" + "oauth_nonce=" + GetNonce();
            sigBaseStringParams += "&" + "oauth_signature_method=" + "HMAC-SHA1";
            sigBaseStringParams += "&" + "oauth_timestamp=" + GetTimeStamp();
            sigBaseStringParams += "&" + "oauth_callback=http%3A%2F%2Flocal%3Fdump";
            sigBaseStringParams += "&" + "oauth_version=1.0";
            string sigBaseString = "POST&";
            sigBaseString += Uri.EscapeDataString(requestUrl) + "&" + Uri.EscapeDataString(sigBaseStringParams);

            string signature = GetSignature(sigBaseString, secret);

            Console.WriteLine(PostData(requestUrl, sigBaseStringParams + "&oauth_signature=" + Uri.EscapeDataString(signature)));
            Console.ReadKey();
        }

        public static string GetNonce()
        {
            return new Random().Next(1000000000).ToString();
        }

        public static string GetTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }

        public static String PostData(string url, string postData)
        {
            WebClient w = new WebClient();
            return w.UploadString(url, postData);
        }

        public static string GetSignature(string sigBaseString, string consumerSecretKey, string requestTokenSecretKey = null)
        {
            HMACSHA1 hmacsha1 = new HMACSHA1();
            String signingKey = string.Format("{0}&{1}", consumerSecretKey, !string.IsNullOrEmpty(requestTokenSecretKey) ? requestTokenSecretKey : "");
            hmacsha1.Key = Encoding.UTF8.GetBytes(signingKey);            
            return Convert.ToBase64String(hmacsha1.ComputeHash(Encoding.UTF8.GetBytes(sigBaseString)));
        }

    }
}

编辑:这是按请求捕获的提琴手和浏览器:

提琴手和浏览器

提前致谢!

编辑 2:我使用 n0741337 的建议提供的OAuthBase.cs类进行了新测试,但是,该类不符合要求回调方法的 1.0A 规范(至少 Bitbucket 可以这么说这样的参数是必需的)所以我不得不修改它,以便它在基本签名字符串中包含回调参数(以未编码的原始格式)。虽然结果相同(我认为如果我显示我的密钥并不重要,因为无论如何你都不会看到我的密钥),这是一个捕获:

提琴手2

这是签名基本字符串:

GET&https%3A%2F%2Fbitbucket.org%2Fapi%2F1.0%2Foauth%2Frequest_token&oauth_callback%3Dhttp%3A%2F%2Flocalhost%3A4000%2F%26oauth_consumer_key%3Dkey%26oauth_nonce%3D8231861%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1423671576%26oauth_version%3D1.0

另外,为了让我的代码受到质疑,也可以使用我发现有人已经适应 Twitter 的这个类(尽管 Twitter 在我的工作场所被阻止,所以我无法对其进行测试),但结果是一样的。

这是我制作的使用此类类的新代码:

using System;
using System.Net;
using System.Security.Cryptography;
using OAuth;

namespace OAuthTest1
{
    public class oauth2
    {

        public static void Run()
        {
            OAuthBase a = new OAuthBase();
            String nonce = a.GenerateNonce();
            String ts = a.GenerateTimeStamp();
            String key = "key";
            String secret = "secret";
            String requestUrl = "https://bitbucket.org/api/1.0/oauth/request_token";

            String normalizedUrl, normalizedArgs;
            String sigBase = a.GenerateSignatureBase(
                new Uri(requestUrl),
                key,
                null,
                secret,
                "http://localhost:4000/",
                "GET",
                ts,
                nonce,
                "HMAC-SHA1",
                out normalizedUrl,
                out normalizedArgs
            );
            String sig = a.GenerateSignatureUsingHash(sigBase, new HMACSHA1());

            String GETArgs = String.Empty;
            GETArgs += "oauth_consumer_key=" + key;
            GETArgs += "&oauth_nonce=" + nonce;
            GETArgs += "&oauth_signature_method=HMAC-SHA1";
            GETArgs += "&oauth_timestamp=" + ts;
            GETArgs += "&oauth_version=1.0";
            GETArgs += "&oauth_callback=" + a.UrlEncode("http://localhost:4000/");
            GETArgs += "&oauth_signature=" + sig;

            WebClient w = new WebClient();
            Console.WriteLine(w.DownloadString(requestUrl + "?" + GETArgs));

            Console.ReadKey();
        }

    }
}

编辑 2 的问题:有谁知道通过这种方法连接到 Bitbucket 服务的应用程序,以便我可以运行 Fiddler 并查看它发送的内容?如果我能看到一些输出,我至少可以复制流程:/ 我已经尝试过 SourceTree,但效果不是很好。

编辑 3:根据 AZ. 的建议,我用这个更改了时间戳生成代码,但它无论如何都不起作用:(。时间戳值看起来不错,但我的时间戳和服务器的时间戳之间只有 5 秒的细微差别:

TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
String timeStamp = Convert.ToInt64(ts.TotalSeconds).ToString();

另外,我注意到签名包含一个“+”,它应该被编码为 %20,当我在编辑问题后注意到这一点时我这样做了,它也不起作用,仅供参考。

提琴手3

4

3 回答 3

1

不确定这是否是原因,但这条线很臭:

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

您正在从 UTC 中减去 DateTimeKind.Unspecified 日期。我没有可用于测试的环境,但构建 1970 年的日期作为 UTC 可能会有所帮助

于 2015-02-11T17:05:45.107 回答
1

我怀疑您的问题是您正在本地开发服务器上调试它,而您作为回调 URL 发送的这个 URL 对 Internet 上的客户端无用:

GETArgs += "&oauth_callback=" + a.UrlEncode("http://localhost:4000/");

我还没有阅读 BitBucket 的文档,但 Google OAuth 的实现方式是您需要有一个可公开访问的 URL 来发送回调消息,以便握手工作。

此 URL 不会被您的浏览器回调,它是从他们的服务器调用的,因此它必须是 Internet 可访问的。

于 2015-02-17T17:09:45.270 回答
0

There are 2 things wrong with your program:

  • You're not sending the Content-Type: application/x-www-form-urlencoded header, which makes the POST body unintelligible (it's generally recommended to use the Authorization request header).
  • You are not creating the signature base string correctly. The signature base string is the most delicate part of OAuth 1.0, as both client and server MUST independently produce exactly the same byte sequence. With the oauth_callback you got the ordering of the parameters wrong (http://oauth.net/core/1.0/#anchor14)

Here's the patch to make the program work:

--- Program.cs  2015-02-23 23:13:03.000000000 -0800
+++ Program3.cs 2015-02-23 23:20:37.000000000 -0800
@@ -15,11 +15,12 @@
             String secret = "SECRET";
             String requestUrl = "https://bitbucket.org/api/1.0/oauth/request_token";

-            string sigBaseStringParams = "oauth_consumer_key=" + key;
+            string sigBaseStringParams = "";
+            sigBaseStringParams += "oauth_callback=http%3A%2F%2Flocal%3Fdump";
+            sigBaseStringParams += "&" + "oauth_consumer_key=" + key;
             sigBaseStringParams += "&" + "oauth_nonce=" + GetNonce();
             sigBaseStringParams += "&" + "oauth_signature_method=" + "HMAC-SHA1";
             sigBaseStringParams += "&" + "oauth_timestamp=" + GetTimeStamp();
-            sigBaseStringParams += "&" + "oauth_callback=http%3A%2F%2Flocal%3Fdump";
             sigBaseStringParams += "&" + "oauth_version=1.0";
             string sigBaseString = "POST&";
             sigBaseString += Uri.EscapeDataString(requestUrl) + "&" + Uri.EscapeDataString(sigBaseStringParams);
@@ -44,6 +45,7 @@
         public static String PostData(string url, string postData)
         {
             WebClient w = new WebClient();
+            w.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
             return w.UploadString(url, postData);
         }
于 2015-02-24T07:22:18.877 回答