GET fails with 401 (Unauthorized) when query parameter is involved due to invalid OAuth signature

本小妞迷上赌 提交于 2021-02-08 07:53:50

问题


I'm querying an API that uses OAuth1. I can make a query successfully using 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);

Unfortunately I'm unable to use RestSharp in the actual project (I'm just using it here to verify that the API call works) so I've tried to get OAuth working using just plain .NET.

I have it working for requests that do not utilise query parameters, so a straightforward GET to https://api.thirdparty.com/1/projects/12345/documents works just fine.

As soon as I try to use a query parameter such as https://api.../documents?recursive=true as shown in the RestSharp sample I get a 401 Unauthorized error as I think my OAuth signature is not valid.

Here is how I'm generating the OAuth signature and request. Can anybody tell me how to generate a valid signature when query parameters are involved?

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)
    );
}

回答1:


In oauth 1, query string parameters (and also POST parameters in case you POST with x-www-form-urlencoded, so they are like "a=1&b=2" in body) should be included in list of key-value pairs which you then sort and sign. So to get correct signature you have to:

  • extract all query string (and POST, as above) parameters as key-value pairs
  • remove query string from url
  • sign all that as you are doing now (url without query string, and all keypairs , including extracted above and oauth-specific)


来源:https://stackoverflow.com/questions/49677370/get-fails-with-401-unauthorized-when-query-parameter-is-involved-due-to-invali

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!