我一直在敲桌子大约一个星期试图让BitBucket OAuth API工作,我甚至无法获得一个请求令牌来保存自己的生命。我在这里制作的代码适用于LinkedIn,但BitBucket总是返回HTTP状态代码400,其中包含一个非常详细且有用的消息:“无法验证OAuth请求”。
这是一个简单的代码(50行,不能缩短它),这是因为我需要手动实现OAuth,否则我不会在这里询问并使用其他外部库但是由于公司的要求我在这个项目中不允许使用外部库。我没有看到什么是错的,1.0a并不那么难,获得一个请求令牌不应该花这么长时间。可能有什么不对?
我还检查了我的时间戳,这很好,针对pool.ntp.org的w32tm.exe以+30或其他东西返回时间。我也试过添加和删除UtcNow时间戳30分钟没有成功,但我的时钟正确同步(当地时间和正确的GMT值(格林威治标准时间-4:30))所以它完全没有意义。
可能是因为我在公司防火墙(Forefront)后面?但是,为什么LinkedIn的API调用工作和BitBucket不工作?我还阅读了一篇很多的文件,例如OAuth Bible,RFC,官方文档等。当然,在询问并查看所有>之前,我们对SO进行了广泛的搜索。 strong>在点击“发布问题”按钮之前,“类似问题”面板中显示的链接。
这是简单的代码(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很友好地说这样的参数是必需的)所以我不得不修改它,因此它包含了基本签名字符串中的回调参数(原始格式没有编码)。虽然结果相同(我认为如果我显示我的密钥并不重要,因为你不会看到我的秘密密钥),这是一个捕获:
这是签名基本字符串:
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在我的工作场所被封锁,所以我无法对其进行测试),但结果是一样的。
这是我使用这些类创建的新代码:
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,我在编辑问题时注意到这一点后就做了,但它也不起作用,只是FYI。
答案 0 :(得分:1)
不确定这是否是原因,但这条线很臭:
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
您正在从UTC中减去DateTimeKind.Unspecified日期。我没有可用于测试此环境的环境,但是将UTC构建1970日期可能会有所帮助
答案 1 :(得分:1)
我怀疑您的问题是您在本地开发服务器上调试此问题,并且您作为回调URL发送的此URL对于Internet上的客户端无用:
GETArgs += "&oauth_callback=" + a.UrlEncode("http://localhost:4000/");
我还没有读过BitBucket的文档,但Google OAuth的实现方式是,您需要有一个可公开访问的URL,以便将回调消息发送到该地址,以便握手能够正常工作。 / p>
此网址不会被您的浏览器回叫,而是从其服务器调用,因此必须可以通过Internet访问。
答案 2 :(得分:0)
您的计划有两个问题:
Content-Type: application/x-www-form-urlencoded
标头,这使得POST机构难以理解(通常建议使用授权请求标头)。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);
}