0

我正在尝试通过 oauth(.net core C#)访问我的 Withings/Nokia 缩放数据。

可以在以下位置找到说明: https ://oauth.withings.com/en/api/oauthguide

以及此处的 API 指南: https ://developer.health.nokia.com/api#step1

我已经完成了第 1 部分 - 我得到了一个身份验证令牌和秘密。

第 2 部分 - 我手动检索了一个代码,授权我的应用程序使用我的 withings scales 数据 - 即作为回调结果的身份验证代码(通过 API 开发人员页面)。我假设这只需要做一次就可以永久授权我的应用程序的访问。我已将此值硬编码到我的代码中,并在我重新授权应用程序时更新它。

现在我被困在第 3 部分 - 获取访问令牌/秘密。错误 = 无效签名

(使用上面的页面,我已经能够检索到我 4 年的秤数据,所以我知道它应该可以工作)。

我的基本签名与上面的 API 测试页面相同(除了随机数、签名和时间戳)。

我的 url 与上面的 API 测试页面相同(除了随机数和时间戳)。

对我来说,谜团在于为什么这适用于第 1 部分而不适用于第 3 部分。是代码不好还是仅仅是请求令牌必须针对应用程序/用户数据进行授权才能发出请求?但是我肯定不必每次都向用户重新授权??

我最初搞砸了第 1 部分并给出了无效的签名错误——这显然是签名的问题——但我已经重新检查了第 3 部分中的签名,这很好。

private const string AUTH_VERSION = "1.0";
private const string SIGNATURE_METHOD = "HMAC-SHA1";

private const string BASE_URL_REQUEST_AUTH_TOKEN = "https://developer.health.nokia.com/account/request_token";
private const string BASE_URL_REQUEST_ACCESS_TOKEN = "https://developer.health.nokia.com/account/access_token";

...

Withings w = new Withings();
OAuthToken t = await w.GetOAuthToken();
string token = t.OAuth_Token;
string secret = t.OAuth_Token_Secret;
OAuthAccessToken at = await w.GetOAuthAccess(t);
string aToken = at.OAuth_Token;
string aTokenSecret = at.OAuth_Token_Secret;

...

public async Task<OAuthAccessToken> GetOAuthAccess(OAuthToken authToken)
        {
            OAuthAccessToken token = new OAuthAccessToken();

            try
            {
                string random = GetRandomString();
                string timestamp = GetTimestamp();

                string baseSignature = GetOAuthAccessSignature(authToken, random, timestamp);
                string hashSignature = ComputeHash(baseSignature, CONSUMER_SECRET, authToken.OAuth_Token_Secret);
                string codeSignature = UrlEncode(hashSignature);

                string requestUrl = GetOAuthAccessUrl(authToken, codeSignature, random, timestamp);

                HttpResponseMessage response = await client.GetAsync(requestUrl);
                string responseBodyAsText = await response.Content.ReadAsStringAsync();

                string[] parameters = responseBodyAsText.Split('&');
                token.OAuth_Token = parameters[0].Split('=')[1].ToString();
                token.OAuth_Token_Secret = parameters[1].Split('=')[1].ToString();

            }
            catch (Exception ex)
            {

            }
            return token;
        }

private string GetOAuthAccessSignature(OAuthToken authToken, string random, string timestamp)
        {
            var urlDict = new SortedDictionary<string, string>
            {
                //{ "oauth_consumer_key", CONSUMER_KEY},
                { "oauth_nonce", random},
                { "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)},
                { "oauth_timestamp", timestamp},
                { "oauth_token", END_USER_AUTHORISATION_REQUEST_TOKEN },
                { "oauth_version", AUTH_VERSION}
            };

            StringBuilder sb = new StringBuilder();
            sb.Append("GET&" + UrlEncode(BASE_URL_REQUEST_ACCESS_TOKEN) + "&oauth_consumer_key%3D" + CONSUMER_KEY);

            int count = 0;
            foreach (var urlItem in urlDict)
            {
                count++;
                if (count >= 1) sb.Append(UrlEncode("&"));
                sb.Append(UrlEncode(urlItem.Key + "=" + urlItem.Value));
            }

            return sb.ToString();
        }

        private string GetOAuthAccessUrl(OAuthToken authToken, string signature, string random, string timestamp)
        {
            var urlDict = new SortedDictionary<string, string>
            {
                { "oauth_consumer_key", CONSUMER_KEY},
                { "oauth_nonce", random},
                { "oauth_signature", signature },
                { "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)},
                { "oauth_timestamp", timestamp},
                { "oauth_token", END_USER_AUTHORISATION_REQUEST_TOKEN },
                { "oauth_version", AUTH_VERSION}
            };

            StringBuilder sb = new StringBuilder();
            sb.Append(BASE_URL_REQUEST_ACCESS_TOKEN + "?");

            int count = 0;
            foreach (var urlItem in urlDict)
            {
                count++;
                if (count > 1) sb.Append("&");
                sb.Append(urlItem.Key + "=" + urlItem.Value);
            }

            return sb.ToString();
        }

笔记:

  • 我已经订购了我的参数
  • 我有一个不错的 urlencode(纠正我的错误)
  • 我有一个 hmac-sha1 散列(纠正我错了)
  • 对使用开放库不感兴趣 - 我想在没有第三方工具的情况下修复此代码

以下是我正在使用的辅助方法:

private string ComputeHash(string data, string consumerSecret, string tokenSecret = null)
        {
            // Construct secret key based on consumer key (and optionally include token secret)
            string secretKey = consumerSecret + "&";
            if (tokenSecret != null) secretKey += tokenSecret;

            // Initialise with secret key
            System.Security.Cryptography.HMACSHA1 hmacsha = new System.Security.Cryptography.HMACSHA1(Encoding.ASCII.GetBytes(secretKey));
            hmacsha.Initialize();

            // Convert data into byte array
            byte[] dataBuffer = Encoding.ASCII.GetBytes(data);

            // Computer hash of data byte array
            byte[] hashBytes = hmacsha.ComputeHash(dataBuffer);

            // Return the base 64 of the result
            return Convert.ToBase64String(hashBytes);
        }

        // Get random string
        private string GetRandomString()
        {
            return Guid.NewGuid().ToString().Replace("-", "");
        }

        // Get timestamp
        private string GetTimestamp()
        {
            var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }

        // Url Encode (as Uri.Escape is reported to be not appropriate for this purpose)
        protected string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
        protected string UrlEncode(string value)
        {
            var result = new StringBuilder();
            foreach (var symbol in value)
            {
                if (UnreservedChars.IndexOf(symbol) != -1)
                    result.Append(symbol);
                else
                    result.Append('%' + $"{(int)symbol:X2}");
            }
            return result.ToString();
        }

谢谢,丹。

4

1 回答 1

2

已解决 - 这是我对 oauth 工作原理的理解的问题。

第 1 步- 获取令牌(这允许您根据您的 api 帐户应用程序发出请求)

第 2 步- 创建一个 URL(使用上面的 2 分钟令牌)重定向用户以授权您的 Withings api 应用程序使用特定用户的帐户。在您传递它时返回相同的令牌 - 但现在将允许它在步骤 3 中发出请求。

第 3 步- 请求访问令牌 - 这将为您提供一个令牌和秘密字符串,允许您继续访问此用户的帐户(用于您的 api 帐户应用程序)。

第 4 步- 请求数据 - 方法与所有前面的步骤类似 - 非常简单。返回一大长串数​​据。阅读 API 文档,因为您可以过滤 - 这就是我将要做的,因为我有大约 4/5 年的“有趣”重量数据。

我正在执行第 1 步,然后执行第 3 步,认为从第 2 步返回的代码(没有注意到它与输入的代码相同)可以存储并用于第 3 步,而无需重新授权。

您实际上可以(以及我所做的)是按照 API 演示界面在步骤 3 中生成身份验证令牌和机密,这就是您继续请求数据所需的全部内容。您只需要一次用户授权,并针对用户帐户/某种商店存储第 3 步身份验证令牌/秘密。

另请注意,您可以调用通知 - 新的权重会触发您的网站自动刷新数据。如果您只想登录并查看最新数据而无需手动触发数据刷新/导致进一步延迟,那就太好了。

请注意 API 具有过滤选项 - 因此请确保您阅读了这些选项。

这是一些可能有用的(基本)代码:

        public async Task<string> GetData_BodyMeasures()
    {
        string rawdata = "";

        try
        {
            string random = GetRandomString();
            string timestamp = GetTimestamp();

            string baseSignature = GetDataSignature_BodyMeasure(random, timestamp);
            string hashSignature = ComputeHash(baseSignature, CONSUMER_SECRET, ACCESS_OAUTH_TOKEN_SECRET);
            string codeSignature = UrlEncode(hashSignature);

            string requestUrl = GetData_BodyMeasure_Url(codeSignature, random, timestamp);

            HttpResponseMessage response = await client.GetAsync(requestUrl);
            string responseBodyAsText = await response.Content.ReadAsStringAsync();

            rawdata = responseBodyAsText;

        }
        catch (Exception ex)
        {

        }
        return rawdata;
    }




    private string GetDataSignature_BodyMeasure(string random, string timestamp)
    {
        var urlDict = new SortedDictionary<string, string>
        {
            { "oauth_consumer_key", CONSUMER_KEY},
            { "oauth_nonce", random},
            { "oauth_signature_method", SIGNATURE_METHOD},
            { "oauth_timestamp", timestamp},
            { "oauth_token", ACCESS_OAUTH_TOKEN },
            { "oauth_version", AUTH_VERSION},
            { "userid", USER_ID }
        };

        StringBuilder sb = new StringBuilder();
        sb.Append("GET&" + UrlEncode(BASE_URL_REQUEST_BODY_MEASURE) + "&action%3Dgetmeas");

        int count = 0;
        foreach (var urlItem in urlDict)
        {
            count++;
            if (count >= 1) sb.Append(UrlEncode("&"));
            sb.Append(UrlEncode(urlItem.Key + "=" + urlItem.Value));
        }

        return sb.ToString();
    }


    private string GetData_BodyMeasure_Url(string signature, string random, string timestamp)
    {
        var urlDict = new SortedDictionary<string, string>
        {
            { "action", "getmeas"},
            { "oauth_consumer_key", CONSUMER_KEY},
            { "oauth_nonce", random},
            { "oauth_signature", signature },
            { "oauth_signature_method", UrlEncode(SIGNATURE_METHOD)},
            { "oauth_timestamp", timestamp},
            { "oauth_token", ACCESS_OAUTH_TOKEN },
            { "oauth_version", AUTH_VERSION},
            { "userid", USER_ID }
        };

        StringBuilder sb = new StringBuilder();
        sb.Append(BASE_URL_REQUEST_BODY_MEASURE + "?");

        int count = 0;
        foreach (var urlItem in urlDict)
        {
            count++;
            if (count >= 1) sb.Append("&");
            sb.Append(urlItem.Key + "=" + urlItem.Value);
        }

        return sb.ToString();
    }
于 2017-07-28T21:48:37.333 回答