当请求包含冒号字符时,Twitter 单用户 OAuth 失败
Twitter Single User OAuth fails when request has a colon character
我正在尝试使用 Twitter 的单用户 OAuth 向 Twitter API 发出搜索。我发现了这个有用的库,让我可以这样做:https://gist.github.com/EelcoKoster/326aa7d1b1338806b058ddcc404622b6
(基于此处的原始作品:https://blog.dantup.com/2016/07/simplest-csharp-code-to-post-a-tweet-using-oauth)
但是,虽然我可以使用此库发送查询并获得纯文本搜索的结果,但当我尝试查询某个位置时它会阻塞,例如:geocode:26.201461,-98.237987,0.25mi
经过一些测试,似乎是冒号 : 字符导致了问题。从查询中删除它会得到结果(因为 twitter 中没有这样的字符串,所以为空,但成功);将其添加回去会出现错误:无法对您进行身份验证。
我尝试使用参数的编码,但没有任何进展。
我相信这可以做到,因为 API 控制台似乎正在使用相同的授权:https://dev.twitter.com/rest/tools/console
而且我可以进行这样的搜索,所以我的签名肯定有问题。
这是相关代码(我已经硬编码了我的查询以进行测试):
public Task<string> Search(string search)
{
// search = Uri.EscapeDataString(search);
return SendRequest("search/tweets.json?q=geocode:26.201461,-98.237987,0.25mi", HttpMethod.GET, new Dictionary<string, string>());
}
Task<string> SendRequest(string url, HttpMethod httpMethod, Dictionary<string, string> data)
{
var fullUrl = TwitterApiBaseUrl + url;
Random rand = new Random();
// Timestamps are in seconds since 1/1/1970.
var timestamp = (int) ((DateTime.UtcNow - epochUtc).TotalSeconds);
// Add all the OAuth headers and querystring parameters, we'll need to use when constructing the hash.
var query = url.Split('?');
if (query.Count() > 1)
{
if (data == null) data = new Dictionary<string, string>();
var pairs = query[1].Split('&');
foreach (var pair in pairs)
{
var keyvalue = pair.Split('=');
data.Add(keyvalue[0], keyvalue[1]);
}
}
data.Add("oauth_consumer_key", consumerKey);
data.Add("oauth_signature_method", "HMAC-SHA1");
data.Add("oauth_timestamp", timestamp.ToString());
data.Add("oauth_nonce", rand.Next(10000000, 999999999).ToString());
data.Add("oauth_token", accessToken);
data.Add("oauth_version", "1.0");
// Generate the OAuth signature and add it to our payload.
data.Add("oauth_signature", GenerateSignature(fullUrl, data, httpMethod));
// Build the OAuth HTTP Header from the data.
string oAuthHeader = GenerateOAuthHeader(data);
switch (httpMethod)
{
case HttpMethod.GET:
return SendRequest(fullUrl, oAuthHeader, null, httpMethod);
case HttpMethod.POST:
var formData = new FormUrlEncodedContent(data.Where(kvp => !kvp.Key.StartsWith("oauth_")));
return SendRequest(fullUrl, oAuthHeader, formData, httpMethod);
default: return null;
}
}
/// <summary>
/// Generate an OAuth signature from OAuth header values.
/// </summary>
string GenerateSignature(string url, Dictionary<string, string> data, HttpMethod httpMethod)
{
var sigString = string.Join(
"&",
data
.Union(data)
.Select(kvp => string.Format("{0}={1}", WebUtility.UrlEncode(kvp.Key), WebUtility.UrlEncode(kvp.Value)))
.OrderBy(s => s)
);
string urlWithoutParameters = url.Split('?')[0];
var fullSigData = string.Format(
"{0}&{1}&{2}",
httpMethod.ToString(),
Uri.EscapeDataString(urlWithoutParameters),
Uri.EscapeDataString(sigString.ToString())
);
return Convert.ToBase64String(sigHasher.ComputeHash(new ASCIIEncoding().GetBytes(fullSigData.ToString())));
}
/// <summary>
/// Generate the raw OAuth HTML header from the values (including signature).
/// </summary>
string GenerateOAuthHeader(Dictionary<string, string> data)
{
return "OAuth " + string.Join(
",",
data
.Where(kvp => kvp.Key.StartsWith("oauth_"))
.Select(kvp => string.Format("{0}=\"{1}\"", Uri.EscapeDataString(kvp.Key), Uri.EscapeDataString(kvp.Value)))
.OrderBy(s => s)
);
}
谁能看出我做错了什么?
aha 在发布后不久就发现了解决方案。在我的例子中,问题是查询字符串正在被编码,但显然必须与发出请求的 url 完全匹配。由于我没有对 url 进行编码以匹配,因此出现错误。
我需要修改代码以对原始 URL 中的查询字符串值进行 UrlEncode,以便它们与签名中提交的内容相匹配。
这是一个让我能够继续的快速而肮脏的解决方案:
Task<string> SendRequest(string url, HttpMethod httpMethod, Dictionary<string, string> data)
{
var fullUrl = TwitterApiBaseUrl;
// ...
var query = url.Split('?');
if (query.Count() > 1)
{
fullUrl += query[0] + "?";
if (data == null) data = new Dictionary<string, string>();
var pairs = query[1].Split('&');
foreach (var pair in pairs)
{
var keyvalue = pair.Split('=');
data.Add(keyvalue[0], keyvalue[1]);
fullUrl += keyvalue[0] + "=" + WebUtility.UrlEncode(keyvalue[1]) + "&";
}
fullUrl = fullUrl.TrimEnd('&');
}
else
{
fullUrl += url;
}
// ...
}
希望这对其他人有帮助!
我正在尝试使用 Twitter 的单用户 OAuth 向 Twitter API 发出搜索。我发现了这个有用的库,让我可以这样做:https://gist.github.com/EelcoKoster/326aa7d1b1338806b058ddcc404622b6
(基于此处的原始作品:https://blog.dantup.com/2016/07/simplest-csharp-code-to-post-a-tweet-using-oauth)
但是,虽然我可以使用此库发送查询并获得纯文本搜索的结果,但当我尝试查询某个位置时它会阻塞,例如:geocode:26.201461,-98.237987,0.25mi
经过一些测试,似乎是冒号 : 字符导致了问题。从查询中删除它会得到结果(因为 twitter 中没有这样的字符串,所以为空,但成功);将其添加回去会出现错误:无法对您进行身份验证。
我尝试使用参数的编码,但没有任何进展。
我相信这可以做到,因为 API 控制台似乎正在使用相同的授权:https://dev.twitter.com/rest/tools/console
而且我可以进行这样的搜索,所以我的签名肯定有问题。
这是相关代码(我已经硬编码了我的查询以进行测试):
public Task<string> Search(string search)
{
// search = Uri.EscapeDataString(search);
return SendRequest("search/tweets.json?q=geocode:26.201461,-98.237987,0.25mi", HttpMethod.GET, new Dictionary<string, string>());
}
Task<string> SendRequest(string url, HttpMethod httpMethod, Dictionary<string, string> data)
{
var fullUrl = TwitterApiBaseUrl + url;
Random rand = new Random();
// Timestamps are in seconds since 1/1/1970.
var timestamp = (int) ((DateTime.UtcNow - epochUtc).TotalSeconds);
// Add all the OAuth headers and querystring parameters, we'll need to use when constructing the hash.
var query = url.Split('?');
if (query.Count() > 1)
{
if (data == null) data = new Dictionary<string, string>();
var pairs = query[1].Split('&');
foreach (var pair in pairs)
{
var keyvalue = pair.Split('=');
data.Add(keyvalue[0], keyvalue[1]);
}
}
data.Add("oauth_consumer_key", consumerKey);
data.Add("oauth_signature_method", "HMAC-SHA1");
data.Add("oauth_timestamp", timestamp.ToString());
data.Add("oauth_nonce", rand.Next(10000000, 999999999).ToString());
data.Add("oauth_token", accessToken);
data.Add("oauth_version", "1.0");
// Generate the OAuth signature and add it to our payload.
data.Add("oauth_signature", GenerateSignature(fullUrl, data, httpMethod));
// Build the OAuth HTTP Header from the data.
string oAuthHeader = GenerateOAuthHeader(data);
switch (httpMethod)
{
case HttpMethod.GET:
return SendRequest(fullUrl, oAuthHeader, null, httpMethod);
case HttpMethod.POST:
var formData = new FormUrlEncodedContent(data.Where(kvp => !kvp.Key.StartsWith("oauth_")));
return SendRequest(fullUrl, oAuthHeader, formData, httpMethod);
default: return null;
}
}
/// <summary>
/// Generate an OAuth signature from OAuth header values.
/// </summary>
string GenerateSignature(string url, Dictionary<string, string> data, HttpMethod httpMethod)
{
var sigString = string.Join(
"&",
data
.Union(data)
.Select(kvp => string.Format("{0}={1}", WebUtility.UrlEncode(kvp.Key), WebUtility.UrlEncode(kvp.Value)))
.OrderBy(s => s)
);
string urlWithoutParameters = url.Split('?')[0];
var fullSigData = string.Format(
"{0}&{1}&{2}",
httpMethod.ToString(),
Uri.EscapeDataString(urlWithoutParameters),
Uri.EscapeDataString(sigString.ToString())
);
return Convert.ToBase64String(sigHasher.ComputeHash(new ASCIIEncoding().GetBytes(fullSigData.ToString())));
}
/// <summary>
/// Generate the raw OAuth HTML header from the values (including signature).
/// </summary>
string GenerateOAuthHeader(Dictionary<string, string> data)
{
return "OAuth " + string.Join(
",",
data
.Where(kvp => kvp.Key.StartsWith("oauth_"))
.Select(kvp => string.Format("{0}=\"{1}\"", Uri.EscapeDataString(kvp.Key), Uri.EscapeDataString(kvp.Value)))
.OrderBy(s => s)
);
}
谁能看出我做错了什么?
aha 在发布后不久就发现了解决方案。在我的例子中,问题是查询字符串正在被编码,但显然必须与发出请求的 url 完全匹配。由于我没有对 url 进行编码以匹配,因此出现错误。
我需要修改代码以对原始 URL 中的查询字符串值进行 UrlEncode,以便它们与签名中提交的内容相匹配。
这是一个让我能够继续的快速而肮脏的解决方案:
Task<string> SendRequest(string url, HttpMethod httpMethod, Dictionary<string, string> data)
{
var fullUrl = TwitterApiBaseUrl;
// ...
var query = url.Split('?');
if (query.Count() > 1)
{
fullUrl += query[0] + "?";
if (data == null) data = new Dictionary<string, string>();
var pairs = query[1].Split('&');
foreach (var pair in pairs)
{
var keyvalue = pair.Split('=');
data.Add(keyvalue[0], keyvalue[1]);
fullUrl += keyvalue[0] + "=" + WebUtility.UrlEncode(keyvalue[1]) + "&";
}
fullUrl = fullUrl.TrimEnd('&');
}
else
{
fullUrl += url;
}
// ...
}
希望这对其他人有帮助!