使用C#进行REST API身份验证(OAuth 1.0)

7

我正在尝试使用OAuth连接Bricklink REST API(http://apidev.bricklink.com/redmine/projects/bricklink-api/wiki/Authorization)。

理论上应该很简单,但是我当前遇到了困难,一直收到SIGNATURE_INVALID错误。下面是我的尝试方法,请问有什么建议吗?

        const string consumerKey = "";
        const string consumerSecret = "";
        const string tokenSecret = "";
        const string tokenValue = "";
        const string url = "https://api.bricklink.com/api/store/v1/items/part/3001";

        var httpWebRequest = (HttpWebRequest) WebRequest.Create(url);
        httpWebRequest.Method = "GET";

        var timeStamp = ((int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds).ToString();
        var nonce = Convert.ToBase64String(Encoding.UTF8.GetBytes(timeStamp));

        var signatureBaseString = httpWebRequest.Method.ToUpper() + "&";
        signatureBaseString = signatureBaseString + url.ToLower() + "&";
        signatureBaseString = signatureBaseString + "oauth_consumer_key=" + consumerKey + "&";
        signatureBaseString = signatureBaseString + "oauth_nonce=" + nonce + "&";
        signatureBaseString = signatureBaseString + "oauth_signature_method=" + "HMAC-SHA1" + "&";
        signatureBaseString = signatureBaseString + "oauth_timestamp=" + timeStamp + "&";
        signatureBaseString = signatureBaseString + "oauth_token=" + tokenValue + "&";
        signatureBaseString = signatureBaseString + "oauth_version=" + "1.0";
        signatureBaseString = Uri.EscapeDataString(signatureBaseString);
        Console.WriteLine(signatureBaseString);

        var signatureEncoding = new ASCIIEncoding();
        var keyBytes = signatureEncoding.GetBytes(consumerSecret + "&" + tokenSecret);
        var signatureBaseBytes = signatureEncoding.GetBytes(signatureBaseString);
        string signatureString;
        using (var hmacsha1 = new HMACSHA1(keyBytes))
        {
            var hashBytes = hmacsha1.ComputeHash(signatureBaseBytes);
            signatureString = Convert.ToBase64String(hashBytes);
        }
        signatureString = Uri.EscapeDataString(signatureString);
        Console.WriteLine(signatureString);

        string SimpleQuote(string x) => '"' + x + '"';
        var header =
            "OAuth realm=" + SimpleQuote("") + "," +
            "oauth_consumer_key=" + SimpleQuote(consumerKey) + "," +
            "oauth_nonce=" + SimpleQuote(nonce) + "," +
            "oauth_signature_method=" + SimpleQuote("HMAC-SHA1") + "," +
            "oauth_timestamp=" + SimpleQuote(timeStamp) + "," +
            "oauth_token=" + SimpleQuote(tokenValue) + "," +
            "oauth_version=" + SimpleQuote("1.0") + "," +
            "oauth_signature= " + SimpleQuote(signatureString);
        Console.WriteLine(header);
        httpWebRequest.Headers.Add(HttpRequestHeader.Authorization, header);

        var response = httpWebRequest.GetResponse();
        var characterSet = ((HttpWebResponse) response).CharacterSet;
        var responseEncoding = characterSet == ""
            ? Encoding.UTF8
            : Encoding.GetEncoding(characterSet ?? "utf-8");
        var responsestream = response.GetResponseStream();
        if (responsestream == null)
        {
            throw new ArgumentNullException(nameof(characterSet));
        }
        using (responsestream)
        {
            var reader = new StreamReader(responsestream, responseEncoding);
            var result = reader.ReadToEnd();
            Console.WriteLine(result);
        }

我知道consumerKey、consumerSecret、tokenSecret和tokenValue是正确的,因为我可以使用JavaScript连接bricklink-api(https://www.npmjs.com/package/bricklink-api)。

3个回答

9

重新学习了一遍https://oauth.net/core/1.0/#signing_process,终于理解了。请注意,转义函数并不是必需的,我仅在尝试使事情工作时偶然发现它。

        const string consumerKey = "";
        const string consumerSecret = "";
        const string tokenSecret = "";
        const string tokenValue = "";
        const string url = "https://api.bricklink.com/api/store/v1/items/part/3001";

        string Escape(string s)
        {
            // https://dev59.com/nHRA5IYBdhLWcg3wvQhh
            var charsToEscape = new[] {"!", "*", "'", "(", ")"};
            var escaped = new StringBuilder(Uri.EscapeDataString(s));
            foreach (var t in charsToEscape)
            {
                escaped.Replace(t, Uri.HexEscape(t[0]));
            }
            return escaped.ToString();
        }

        var httpWebRequest = (HttpWebRequest) WebRequest.Create(url);
        httpWebRequest.Method = "GET";

        var timeStamp = ((int) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds).ToString();
        var nonce = Convert.ToBase64String(Encoding.UTF8.GetBytes(timeStamp));

        var signatureBaseString = Escape(httpWebRequest.Method.ToUpper()) + "&";
        signatureBaseString += EscapeUriDataStringRfc3986(url.ToLower()) + "&";
        signatureBaseString += EscapeUriDataStringRfc3986(
            "oauth_consumer_key=" + EscapeUriDataStringRfc3986(consumerKey) + "&" +
            "oauth_nonce=" + EscapeUriDataStringRfc3986(nonce) + "&" +
            "oauth_signature_method=" + EscapeUriDataStringRfc3986("HMAC-SHA1") + "&" +
            "oauth_timestamp=" + EscapeUriDataStringRfc3986(timeStamp) + "&" +
            "oauth_token=" + EscapeUriDataStringRfc3986(tokenValue) + "&" +
            "oauth_version=" + EscapeUriDataStringRfc3986("1.0"));
        Console.WriteLine(@"signatureBaseString: " + signatureBaseString);

        var key = EscapeUriDataStringRfc3986(consumerSecret) + "&" + EscapeUriDataStringRfc3986(tokenSecret);
        Console.WriteLine(@"key: " + key);
        var signatureEncoding = new ASCIIEncoding();
        var keyBytes = signatureEncoding.GetBytes(key);
        var signatureBaseBytes = signatureEncoding.GetBytes(signatureBaseString);
        string signatureString;
        using (var hmacsha1 = new HMACSHA1(keyBytes))
        {
            var hashBytes = hmacsha1.ComputeHash(signatureBaseBytes);
            signatureString = Convert.ToBase64String(hashBytes);
        }
        signatureString = EscapeUriDataStringRfc3986(signatureString);
        Console.WriteLine(@"signatureString: " + signatureString);

        string SimpleQuote(string s) => '"' + s + '"';
        var header =
            "OAuth realm=" + SimpleQuote("") + "," +
            "oauth_consumer_key=" + SimpleQuote(consumerKey) + "," +
            "oauth_nonce=" + SimpleQuote(nonce) + "," +
            "oauth_signature_method=" + SimpleQuote("HMAC-SHA1") + "," +
            "oauth_timestamp=" + SimpleQuote(timeStamp) + "," +
            "oauth_token=" + SimpleQuote(tokenValue) + "," +
            "oauth_version=" + SimpleQuote("1.0") + "," +
            "oauth_signature= " + SimpleQuote(signatureString);
        Console.WriteLine(@"header: " + header);
        httpWebRequest.Headers.Add(HttpRequestHeader.Authorization, header);

        var response = httpWebRequest.GetResponse();
        var characterSet = ((HttpWebResponse) response).CharacterSet;
        var responseEncoding = characterSet == ""
            ? Encoding.UTF8
            : Encoding.GetEncoding(characterSet ?? "utf-8");
        var responsestream = response.GetResponseStream();
        if (responsestream == null)
        {
            throw new ArgumentNullException(nameof(characterSet));
        }
        using (responsestream)
        {
            var reader = new StreamReader(responsestream, responseEncoding);
            var result = reader.ReadToEnd();
            Console.WriteLine(@"result: " + result);
        }

你已经使用双腿OAuth测试过你的方法了吗? - wannadream
不,BL 只是使用类似 OAuth 1.0 的流程。 - halvorsen

6

对于寻找更简单解决方案的人,这个方法适用于WooCommerce以及可能适用于其他服务。对于WooCommerce,token/tokenSecret为null。

var client = new RestClient($"{StoreHttp}/wp-json/wc/v3/products")
{
    Authenticator = OAuth1Authenticator.ForProtectedResource(ConsumerKey, ConsumerSecret, token, tokenSecret)
};

var request = new RestRequest(Method.GET);
RestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

7
仅供其他读者参考,上面提到的RestClient是来自于RestSharp库。 - MCattle
1
RestSharp完成了任务,感谢@ramunas的提示! - atakan
对于任何其他进行单腿OAuth 1的人,在第3行将tokentokenSecret传递为null。如果需要,可以通过参数5更改签名方法。Authenticator = OAuth1Authenticator.ForProtectedResource(ConsumerKey, ConsumerSecret, null, null, OAuthSignatureMethod.HmacSha256) - Eric Eskildsen

1

使用RestSharp版本108.0.3的示例。

无需编写签名创建代码,RestSharp会为您完成所有操作。

using System;
using RestSharp;
using RestSharp.Authenticators;
using RestSharp.Authenticators.OAuth;
using Newtonsoft.Json;


var client = new RestClient(URL);
var oAuth1 = OAuth1Authenticator.ForAccessToken(
                consumerKey: ConsumerKey,
                consumerSecret: ConsumerSecret,
                token: Token,
                tokenSecret: TokenSecret,
                OAuthSignatureMethod.HmacSha256);
oAuth1.Realm = Realm; // if Realm has otherwise ignore

client.Authenticator = oAuth1;

var request = new RestRequest(URL, Method.Post);
request.AddHeader("Content-Type", "application/json");  
string body = JsonConvert.SerializeObject(bodyObject);
           
request.AddParameter("application/json", body, ParameterType.RequestBody);
var response = client.Execute(request);

我尝试了你的建议,但仍然收到禁止错误。但在Postman中可以工作.. 请有什么建议。我被卡住了.. - anand
@anand请确保您正在使用有效的OAuthSignatureMethod。 - Raju Padhara
谢谢Raju。是的,它起作用了。我也投票支持了这个答案。(y) - anand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接