简体   繁体   English

Withings API-无效的签名

[英]Withings API - Invalid signature

I am trying to get access to my Withings/Nokia scales data via oauth (.net core C#). 我试图通过oauth(.net核心C#)访问我的Withings / Nokia秤数据。

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

And API guide here: https://developer.health.nokia.com/api#step1 此处提供API指南: https//developer.health.nokia.com/api#step1

I have achieved Part 1 - I get an auth token and secret. 我已经实现了第1部分-我获得了身份验证令牌和密码。

Part 2 - manually I have retrieved a code authorizing my app's usage of my withings scales data - ie auth code as a result of the call back (via the API developers page). 第2部分-手动检索了授权我的应用使用我的withings scales数据的代码-即作为回叫结果(通过API开发者页面)的身份验证代码。 I am presuming this only needs to be done once to authorize my app's access permanently. 我想这只需要做一次就可以永久授权我的应用访问。 I have hardcoded this value into my code and update it if I re-authorize the app. 我已将此值硬编码到我的代码中,并且在我重新授权该应用程序时对其进行了更新。

Now I am stuck on Part 3 - getting the access token/secret. 现在我停留在第3部分-获取访问令牌/秘密。 ERROR = Invalid signature 错误=无效的签名

(using the above page I have been able to retrieve my 4 years worth of scales data so I know it should work). (使用上面的页面,我已经能够检索到我4年的秤数据,因此我知道它应该可以工作)。

My base signature is identical to the above API test page (apart from the nonce, signature and timestamp). 我的基本签名与上述API测试页相同(除了随机数,签名和时间戳)。

My url is identical to to the above API test page (apart from the nonce and timestamp). 我的网址与上述API测试页相同(除了随机数和时间戳)。

The mystery for me is why this works for Part 1 and not Part 3. Is it the code that is bad or simply that the request token must be authorized against the application/users data before a request can be made? 对我来说,一个谜是为什么它只适用于第1部分而不适用于第3部分。难道这是不好的代码,还是仅仅是必须对应用程序/用户数据授权请求令牌的代码? But surely I don't have to re-authorize with the user every time?? 但是可以肯定,我不必每次都向用户重新授权?

I originally messed up the Part 1 and gave an invalid signature error - this was clearly an issue with the signature - but I have re-checked the signature in Part 3 and it is good. 我最初弄乱了第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();
        }

Notes: 笔记:

  • I have ordered my parameters 我已经订购了参数
  • I have a decent urlencode (correct me wrong) 我有一个体面的urlencode(纠正我的错误)
  • I have a hmac-sha1 hashing (correct me wrong) 我有一个hmac-sha1哈希(更正了我)
  • Not interested in using open libraries - I want to fix this code without third party tools 对使用开放库不感兴趣-我想在没有第三方工具的情况下修复此代码

Below is the helper methods I am using: 以下是我正在使用的辅助方法:

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

Thanks,Dan. 谢谢,丹。

Solved - this was a problem with my understanding of how oauth works. 解决了-这是我对oauth的工作方式的了解。

Step 1 - Get a token (this allows you to make requests based on your api account application) 第1步 -获取令牌(这使您可以根据自己的api帐户应用程序发出请求)

Step 2 - Create a URL (using the 2 minute token above) that redirects the user to authorize your Withings api applications to use a specific user's account. 第2步 -创建一个URL(使用上面的2分钟标记),该URL重定向用户以授权您的Withings api应用程序使用特定用户的帐户。 The same token is returned as you passed it - but now it will be allowed to make the request in step 3. 传递时会返回相同的令牌-但现在将允许它在步骤3中发出请求。

Step 3 - Requests an access token - this will give you an token and secret string that permits your continued access to this user's account (for your api account application). 第3步 -请求访问令牌-这将为您提供一个令牌和秘密字符串,以允许您继续访问该用户的帐户(适用于您的api帐户应用程序)。

Step 4 - Requesting data - similar in method to all previous steps - quite easy. 第4步 -请求数据-方法与之前的所有步骤类似-非常简单。 Returns a big long string of data. 返回一个大的长字符串。 Read the API documents as you can filter - which is what I will be doing as I have about 4/5 years worth of 'interesting' weight data. 可以过滤以阅读API文档-这就是我将要做的事情,因为我有大约4/5年的“有趣”重量数据。

I was doing step 1 and then doing step 3 thinking that the code returned from step 2 (not having noticed it was the same as the one put in) could be stored and used for step 3 without having to re-authorize. 我正在执行步骤1,然后执行步骤3,以为从步骤2返回的代码(没有注意到它与放入的代码相同)可以被存储并用于步骤3,而无需重新授权。

You can actually (and what I have done) is follow the API demo interface to generate the auth token and secret in step 3 and that's all you need to continue to request data. 实际上,您可以(以及我所做的)是按照API演示界面在步骤3中生成auth令牌和密钥,这就是继续请求数据所需的全部。 You only need user authorization once and store step 3 auth token/secret against a user account / a store of some sort. 您只需要一次用户授权,并针对某个用户帐户/某种存储存储步骤3身份验证令牌/秘密。

Also note that you can invoke notifications - new weight triggers your website to automatically refresh the data. 另请注意,您可以调用通知-新的权重会触发您的网站自动刷新数据。 Great if you just want to login and see the latest data without having the manually trigger a refresh of the data / cause a further delay. 如果您只想登录并查看最新数据,而无需手动触发数据刷新/造成进一步的延迟,则非常好。

Be aware that the API has filtering options - so make sure you read up on those. 请注意,API具有过滤选项-因此请务必仔细阅读这些内容。

Here's some (basic) code which may be of some use: 这是一些(基本)代码,可能有些用处:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM