为什么 BitBucket OAuth request_token API 总是 return 400 "Could not verify OAuth request."?
Why does BitBucket OAuth request_token API always return 400 "Could not verify OAuth request."?
为了让 BitBucket OAuth API 正常工作,我已经用头撞了整整一个星期,但我什至无法获得一个请求令牌来挽救自己的生命。我在这里制作的代码适用于 LinkedIn,但 BitBucket 始终 returns HTTP 状态代码 400 以及非常详细且有用的消息:"Could not verify OAuth request"。
这是一个简单的代码(50 行,不能再短了),就是这样,因为我需要手动实现 OAuth,否则我不会在这里询问并使用其他外部库,但由于公司的要求我不允许在这个项目中使用外部库。不过,我看不出有什么问题,1.0a 并不难,获取请求令牌不应该花这么长时间。有什么问题吗?
我也检查了我的时间戳,它很好,w32tm.exe 对比 pool.ntp.org returns 时间 +30 什么的。我也试过在 UtcNow 时间戳中添加和删除 30 分钟但没有成功,但我的时钟已正确同步(使用本地时间和正确的 GMT 值 (GMT -4:30))所以它根本没有意义。
会不会是因为我在公司防火墙 (Forefront) 后面?但是,为什么 LinkedIn 的 API 调用有效而 BitBucket 却不行?我还阅读了 很多 文档,例如 OAuth 圣经、RFC、官方文档等。当然,在询问之前对 SO 进行了广泛的搜索并查看了 所有点击"Post your question"按钮前"Similar Questions"面板中显示的链接。
这是简单的代码 (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)));
}
}
}
编辑: 这是按要求捕获的 fiddler 和浏览器:
提前致谢!
编辑 2: 我做了一个新的测试,使用了 n0741337 的建议提供的 OAuthBase.cs class,但是 class 确实不符合要求回调方法的 1.0A 规范(至少 Bitbucket 足够友好地说需要这样的参数)所以我不得不修改它以便它在基本签名字符串中包含回调参数(在原始没有编码的格式)。虽然结果相同(我认为如果我显示我的密钥并不重要,因为无论如何你都不会看到我的密钥),这是一个捕获:
这是签名基本字符串:
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
此外,为了质疑我的代码,还可以使用 this class 我发现有人已经针对 Twitter 进行了调整(尽管 Twitter 在我的工作场所被屏蔽,所以我无法对其进行测试),但是结果是一样的。
这是我使用 classes:
编写的新代码
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,我在编辑问题后注意到这一点时就这样做了,但它也不起作用,仅供参考。
不确定是否是这个原因,但这行很臭:
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
您正在从 UTC 日期中减去 DateTimeKind.Unspecified 日期。我没有可用于测试这个的环境,但构建 1970 年的日期作为 UTC 可能会有所帮助
我怀疑你的问题是你正在本地开发服务器上调试它,而你发送的这个 URL 作为回调 URL 对互联网上的客户端是无用的:
GETArgs += "&oauth_callback=" + a.UrlEncode("http://localhost:4000/");
我还没有阅读 BitBucket 的文档,但是 Google OAuth 的实现方式是您需要有一个可公开访问的 URL 以便将回调消息发送到为了让握手工作。
此 URL 不会被您的浏览器回调,它是从 他们的服务器 调用的,因此它必须可以访问 Internet。
你的程序有两处错误:
- 你没有发送
Content-Type: application/x-www-form-urlencoded
header,这使得 POST body 无法理解(通常建议使用授权请求 header ).
- 您没有正确创建签名基本字符串。签名基础字符串是 OAuth 1.0 中最微妙的部分,因为客户端和服务器必须独立地产生完全相同的字节序列。使用
oauth_callback
你得到了错误的参数顺序 (http://oauth.net/core/1.0/#anchor14)
这是使程序运行的补丁:
--- 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);
}
为了让 BitBucket OAuth API 正常工作,我已经用头撞了整整一个星期,但我什至无法获得一个请求令牌来挽救自己的生命。我在这里制作的代码适用于 LinkedIn,但 BitBucket 始终 returns HTTP 状态代码 400 以及非常详细且有用的消息:"Could not verify OAuth request"。
这是一个简单的代码(50 行,不能再短了),就是这样,因为我需要手动实现 OAuth,否则我不会在这里询问并使用其他外部库,但由于公司的要求我不允许在这个项目中使用外部库。不过,我看不出有什么问题,1.0a 并不难,获取请求令牌不应该花这么长时间。有什么问题吗?
我也检查了我的时间戳,它很好,w32tm.exe 对比 pool.ntp.org returns 时间 +30 什么的。我也试过在 UtcNow 时间戳中添加和删除 30 分钟但没有成功,但我的时钟已正确同步(使用本地时间和正确的 GMT 值 (GMT -4:30))所以它根本没有意义。
会不会是因为我在公司防火墙 (Forefront) 后面?但是,为什么 LinkedIn 的 API 调用有效而 BitBucket 却不行?我还阅读了 很多 文档,例如 OAuth 圣经、RFC、官方文档等。当然,在询问之前对 SO 进行了广泛的搜索并查看了 所有点击"Post your question"按钮前"Similar Questions"面板中显示的链接。
这是简单的代码 (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)));
}
}
}
编辑: 这是按要求捕获的 fiddler 和浏览器:
提前致谢!
编辑 2: 我做了一个新的测试,使用了 n0741337 的建议提供的 OAuthBase.cs class,但是 class 确实不符合要求回调方法的 1.0A 规范(至少 Bitbucket 足够友好地说需要这样的参数)所以我不得不修改它以便它在基本签名字符串中包含回调参数(在原始没有编码的格式)。虽然结果相同(我认为如果我显示我的密钥并不重要,因为无论如何你都不会看到我的密钥),这是一个捕获:
这是签名基本字符串:
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
此外,为了质疑我的代码,还可以使用 this class 我发现有人已经针对 Twitter 进行了调整(尽管 Twitter 在我的工作场所被屏蔽,所以我无法对其进行测试),但是结果是一样的。
这是我使用 classes:
编写的新代码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,我在编辑问题后注意到这一点时就这样做了,但它也不起作用,仅供参考。
不确定是否是这个原因,但这行很臭:
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
您正在从 UTC 日期中减去 DateTimeKind.Unspecified 日期。我没有可用于测试这个的环境,但构建 1970 年的日期作为 UTC 可能会有所帮助
我怀疑你的问题是你正在本地开发服务器上调试它,而你发送的这个 URL 作为回调 URL 对互联网上的客户端是无用的:
GETArgs += "&oauth_callback=" + a.UrlEncode("http://localhost:4000/");
我还没有阅读 BitBucket 的文档,但是 Google OAuth 的实现方式是您需要有一个可公开访问的 URL 以便将回调消息发送到为了让握手工作。
此 URL 不会被您的浏览器回调,它是从 他们的服务器 调用的,因此它必须可以访问 Internet。
你的程序有两处错误:
- 你没有发送
Content-Type: application/x-www-form-urlencoded
header,这使得 POST body 无法理解(通常建议使用授权请求 header ). - 您没有正确创建签名基本字符串。签名基础字符串是 OAuth 1.0 中最微妙的部分,因为客户端和服务器必须独立地产生完全相同的字节序列。使用
oauth_callback
你得到了错误的参数顺序 (http://oauth.net/core/1.0/#anchor14)
这是使程序运行的补丁:
--- 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);
}