由于 OAuth 签名无效而涉及查询参数时,GET 失败并返回 401(未授权)
GET fails with 401 (Unauthorized) when query parameter is involved due to invalid OAuth signature
我正在查询使用 OAuth1 的 API。我可以使用 RestSharp 成功进行查询:
var client = new RestClient("https://api.thirdparty.com/1/");
client.Authenticator =
OAuth1Authenticator.ForAccessToken(appKey, appSecret, token, tokenSecret);
var request = new RestRequest("projects/12345/documents", Method.GET);
request.AddParameter("recursive", "true");
var response = client.Execute(request);
不幸的是,我无法在实际项目中使用 RestSharp(我只是在这里使用它来验证 API 调用是否有效)所以我尝试仅使用普通的 .网络
我让它适用于不使用查询参数的请求,因此直接 GET
到 https://api.thirdparty.com/1/projects/12345/documents
就可以正常工作。
当我尝试使用 RestSharp 示例中显示的 https://api.../documents?recursive=true
等查询参数时,我收到 401 Unauthorized
错误,因为我认为我的 OAuth 签名无效。
以下是我生成 OAuth 签名和请求的方式。谁能告诉我如何在涉及查询参数时生成有效签名?
static string appKey = @"e8899de00";
static string appSecret = @"bffe04d6";
static string token = @"6e85a21a";
static string tokenSecret = @"e137269f";
static string baseUrl = "https://api.thirdparty.com/1/";
static HttpClient httpclient;
static HMACSHA1 hasher;
static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
static void Main(string[] args)
{
// SETUP HTTPCLIENT
httpclient = new HttpClient();
httpclient.BaseAddress = new Uri(baseUrl);
httpclient.DefaultRequestHeaders.Accept.Clear();
httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// SETUP THE HASH MESSAGE AUTHENTICATION CODE (HMAC) WITH SHA1
hasher = new HMACSHA1(new ASCIIEncoding().GetBytes(string.Format("{0}&{1}", appSecret, tokenSecret)));
// WORKS IF QUERY PARAMETER IS MISSED OFF THE END, FAILS WITH 401 IF INCLUDED
var document =
Request(HttpMethod.Get, "projects/12345/documents?recursive=true");
}
static string Request(HttpMethod method, string url)
{
// CREATE A TIMESTAMP OF CURRENT EPOCH TIME
var timestamp = (int)((DateTime.UtcNow - epoch).TotalSeconds);
// DICTIONARY WILL HOLD THE KEY/PAIR VALUES FOR OAUTH
var oauth = new Dictionary<string, string>();
oauth.Add("oauth_consumer_key", appKey);
oauth.Add("oauth_signature_method", "HMAC-SHA1");
oauth.Add("oauth_timestamp", timestamp.ToString());
oauth.Add("oauth_nonce", "nonce");
oauth.Add("oauth_token", token);
oauth.Add("oauth_version", "1.0");
// GENERATE OAUTH SIGNATURE
oauth.Add("oauth_signature", GenerateSignature(method.ToString(), string.Concat(baseUrl, url), oauth));
// GENERATE THE REQUEST
using (var request = new HttpRequestMessage())
{
// URL AND METHOD
request.RequestUri = new Uri(string.Concat(baseUrl, url));
request.Method = method;
// GENERATE AUTHORIZATION FOR THIS REQUEST
request.Headers.Add("Authorization", GenerateOAuthHeader(oauth));
// MAKE REQUEST
var response = httpclient.SendAsync(request).Result;
// ENSURE IT WORKED
response.EnsureSuccessStatusCode(); // THROWS 401 UNAUTHORIZED
// RETURN CONTENT
return response.Content.ReadAsStringAsync().Result;
};
}
static string GenerateSignature(string verb, string url, Dictionary<string, string> data)
{
var signaturestring = string.Join(
"&",
data
.OrderBy(s => s.Key)
.Select(kvp => string.Format(
"{0}={1}",
Uri.EscapeDataString(kvp.Key),
Uri.EscapeDataString(kvp.Value))
)
);
var signaturedata = string.Format(
"{0}&{1}&{2}",
verb,
Uri.EscapeDataString(url),
Uri.EscapeDataString(signaturestring.ToString())
);
return Convert.ToBase64String(hasher.ComputeHash(new ASCIIEncoding().GetBytes(signaturedata.ToString())));
}
static string GenerateOAuthHeader(Dictionary<string, string> data)
{
return "OAuth " + string.Join(
", ",
data
.Select(kvp => string.Format("{0}=\"{1}\"", Uri.EscapeDataString(kvp.Key), Uri.EscapeDataString(kvp.Value)))
.OrderBy(s => s)
);
}
在 oauth 1 中,查询字符串参数(以及 POST 参数,如果你 POST 和 x-www-form-urlencoded
,所以它们就像正文中的 "a=1&b=2")应该是包含在 key-value 对的列表中,然后您对其进行排序和签名。因此,要获得正确的签名,您必须:
- 提取所有查询字符串(和 POST,如上所述)参数作为 key-value 对
- 从 url
中删除查询字符串
- 像现在一样签署所有内容(url 没有查询字符串,以及所有密钥对,包括上面提取的和 oauth-specific)
我正在查询使用 OAuth1 的 API。我可以使用 RestSharp 成功进行查询:
var client = new RestClient("https://api.thirdparty.com/1/");
client.Authenticator =
OAuth1Authenticator.ForAccessToken(appKey, appSecret, token, tokenSecret);
var request = new RestRequest("projects/12345/documents", Method.GET);
request.AddParameter("recursive", "true");
var response = client.Execute(request);
不幸的是,我无法在实际项目中使用 RestSharp(我只是在这里使用它来验证 API 调用是否有效)所以我尝试仅使用普通的 .网络
我让它适用于不使用查询参数的请求,因此直接 GET
到 https://api.thirdparty.com/1/projects/12345/documents
就可以正常工作。
当我尝试使用 RestSharp 示例中显示的 https://api.../documents?recursive=true
等查询参数时,我收到 401 Unauthorized
错误,因为我认为我的 OAuth 签名无效。
以下是我生成 OAuth 签名和请求的方式。谁能告诉我如何在涉及查询参数时生成有效签名?
static string appKey = @"e8899de00";
static string appSecret = @"bffe04d6";
static string token = @"6e85a21a";
static string tokenSecret = @"e137269f";
static string baseUrl = "https://api.thirdparty.com/1/";
static HttpClient httpclient;
static HMACSHA1 hasher;
static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
static void Main(string[] args)
{
// SETUP HTTPCLIENT
httpclient = new HttpClient();
httpclient.BaseAddress = new Uri(baseUrl);
httpclient.DefaultRequestHeaders.Accept.Clear();
httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// SETUP THE HASH MESSAGE AUTHENTICATION CODE (HMAC) WITH SHA1
hasher = new HMACSHA1(new ASCIIEncoding().GetBytes(string.Format("{0}&{1}", appSecret, tokenSecret)));
// WORKS IF QUERY PARAMETER IS MISSED OFF THE END, FAILS WITH 401 IF INCLUDED
var document =
Request(HttpMethod.Get, "projects/12345/documents?recursive=true");
}
static string Request(HttpMethod method, string url)
{
// CREATE A TIMESTAMP OF CURRENT EPOCH TIME
var timestamp = (int)((DateTime.UtcNow - epoch).TotalSeconds);
// DICTIONARY WILL HOLD THE KEY/PAIR VALUES FOR OAUTH
var oauth = new Dictionary<string, string>();
oauth.Add("oauth_consumer_key", appKey);
oauth.Add("oauth_signature_method", "HMAC-SHA1");
oauth.Add("oauth_timestamp", timestamp.ToString());
oauth.Add("oauth_nonce", "nonce");
oauth.Add("oauth_token", token);
oauth.Add("oauth_version", "1.0");
// GENERATE OAUTH SIGNATURE
oauth.Add("oauth_signature", GenerateSignature(method.ToString(), string.Concat(baseUrl, url), oauth));
// GENERATE THE REQUEST
using (var request = new HttpRequestMessage())
{
// URL AND METHOD
request.RequestUri = new Uri(string.Concat(baseUrl, url));
request.Method = method;
// GENERATE AUTHORIZATION FOR THIS REQUEST
request.Headers.Add("Authorization", GenerateOAuthHeader(oauth));
// MAKE REQUEST
var response = httpclient.SendAsync(request).Result;
// ENSURE IT WORKED
response.EnsureSuccessStatusCode(); // THROWS 401 UNAUTHORIZED
// RETURN CONTENT
return response.Content.ReadAsStringAsync().Result;
};
}
static string GenerateSignature(string verb, string url, Dictionary<string, string> data)
{
var signaturestring = string.Join(
"&",
data
.OrderBy(s => s.Key)
.Select(kvp => string.Format(
"{0}={1}",
Uri.EscapeDataString(kvp.Key),
Uri.EscapeDataString(kvp.Value))
)
);
var signaturedata = string.Format(
"{0}&{1}&{2}",
verb,
Uri.EscapeDataString(url),
Uri.EscapeDataString(signaturestring.ToString())
);
return Convert.ToBase64String(hasher.ComputeHash(new ASCIIEncoding().GetBytes(signaturedata.ToString())));
}
static string GenerateOAuthHeader(Dictionary<string, string> data)
{
return "OAuth " + string.Join(
", ",
data
.Select(kvp => string.Format("{0}=\"{1}\"", Uri.EscapeDataString(kvp.Key), Uri.EscapeDataString(kvp.Value)))
.OrderBy(s => s)
);
}
在 oauth 1 中,查询字符串参数(以及 POST 参数,如果你 POST 和 x-www-form-urlencoded
,所以它们就像正文中的 "a=1&b=2")应该是包含在 key-value 对的列表中,然后您对其进行排序和签名。因此,要获得正确的签名,您必须:
- 提取所有查询字符串(和 POST,如上所述)参数作为 key-value 对
- 从 url 中删除查询字符串
- 像现在一样签署所有内容(url 没有查询字符串,以及所有密钥对,包括上面提取的和 oauth-specific)