【问题标题】:How to do a PUT or DELETE for Azure Table Storage in NodeJS?如何在 NodeJS 中对 Azure 表存储执行 PUT 或 DELETE?
【发布时间】:2019-11-20 18:51:13
【问题描述】:

所以我一直在从事一个项目,我想为 Azure 表存储构建基本的 CRUD 类功能,但是我一直卡在生成有效的 SharedKeyLite 签名上。

我能够为 GET 和 POST 生成有效的签名,但是当我出于某种奇怪的原因想要 PUT 或 DELETE 和实体时,签名现在无效了吗?

const axios = require('axios');

const client = axios.create({});

const SharedKeyGenerate = require('./SharedKeyGenerator');

const AZURE_STORAGE_KEY = 'STORAGE_KEY';
const AZURE_STORAGE_ACCOUNT = 'igdevharuntest';

const sharedKey = new SharedKeyGenerate(AZURE_STORAGE_ACCOUNT, 'powerbiTableStorage', AZURE_STORAGE_KEY);

client.defaults.baseURL = `https://${AZURE_STORAGE_ACCOUNT}.table.core.windows.net`;
client.defaults.timeout = 2000;

/**
 * get tables from azure table storage
 * with optional filter
 * @param {string} path uri path
 * @param {string} filter filter for the web request
 */
async function getTables(path, filter) {
    try {
        const response = await client.get(`/${path}${filter}`, {
            headers: {
                'x-ms-date': new Date().toUTCString(),
                'Authorization': sharedKey.GenerateSharedKeyLite(path, 'GET', client),
                Accept: 'application/json;odata=nometadata',
                'x-ms-version': '2015-12-11',
            }
        });

        console.log(response.data);
    } catch (err) {
        console.log(err.response.data);
    }
}

async function addEntityToTable(tableName) {
    const payload = {
        'expiresOn': '2019-10-16T17:27:36.046Z',
        'accessToken': 'abcsd123456',
        'PartitionKey': 'NewPartitionKey12',
        'RowKey': 'CompletelyNewKey12'
    };


    try {
        const response = await client.post(`/${tableName}`, payload,{
            headers: {
                'x-ms-date': new Date().toUTCString(),
                'Authorization': sharedKey.GenerateSharedKeyLite(tableName, "POST"),
                Accept: 'application/json;odata=nometadata',
                'x-ms-version': '2015-12-11',
            }
        });

        console.log(response.data);
    } catch (err) {
        console.log(err.response);
    }
}

async function updateEntity(path, filter) {
    const payload = {
        "PartitionKey": "ABCDEFG1234567RANDOMPARTITIONKEY1111",
        "RowKey": "ABCDEFG1234567",
        "expiresOn": "2019-10-16T17:37:07.099Z",
        "accessToken": "eyJ0eXAiOiJKV1QiLCjjJhbGciOiJSUzI1NiIsIng1dCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyIsImtpZCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyJ9.eyJhdWQiOiJodHRwczovL2FuYWx5c2lzLndpbmRvd3MubmV0L3Bvd2VyYmkvYXBpIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvMWMxNzY3MTItMTVmNy00MThjLTk1YWMtNDZmNmE2YTU5NWM1LyIsImlhdCI6MTU3MTI0MzUyNywibmJmIjoxNTcxMjQzNTI3LCJleHAiOjE1NzEyNDc0MjcsImFjY3QiOjAsImFjciI6IjEiLCJhaW8iOiJBU1FBMi84TkFBQUFCQUR6RVIrbHdDT3hPNFAzNzlMQ1dGSjNUUHVVN0Zud1FGaWM2aVpyU05VPSIsImFtciI6WyJwd2QiXSwiYXBwaWQiOiI1NzEzNmRkYy00ZjI3LTQyZTAtYWZmMS1iOGFkOTVlODc0MmEiLCJhcHBpZGFjciI6IjAiLCJmYW1pbHlfbmFtZSI6IlNoZWlraGFsaSIsImdpdmVuX25hbWUiOiJIYXJ1biIsImlwYWRkciI6IjcyLjE0Mi4xOC4zOCIsIm5hbWUiOiJIYXJ1biBTaGVpa2hhbGkiLCJvaWQiOiJjOGZjZDhmNS1jMThmLTQwMGQtODk1OS1jNjFhMjA1NDE1OTYiLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtMjcyMzg1MTQwOC0zODY3MzY4NjQwLTEyMjI5NzYyMDMtMzg0NyIsInB1aWQiOiIxMDAzMDAwMDlBQzQ2QUY1Iiwic2NwIjoiQ2FwYWNpdHkuUmVhZC5BbGwgQ2FwYWNpdHkuUmVhZFdyaXRlLkFsbCBDb250ZW50LkNyZWF0ZSBEYXNoYm9hcmQuUmVhZC5BbGwgRGFzaGJvYXJkLlJlYWRXcml0ZS5BbGwgRGF0YS5BbHRlcl9BbnkgRGF0YXBvb2wuUmVhZC5BbGwgRGF0YXBvb2wuUmVhZFdyaXRlLkFsbCBEYXRhc2V0LlJlYWQuQWxsIERhdGFzZXQuUmVhZFdyaXRlLkFsbCBHcm91cC5SZWFkIEdyb3VwLlJlYWQuQWxsIE1ldGFkYXRhLlZpZXdfQW55IFJlcG9ydC5SZWFkLkFsbCBSZXBvcnQuUmVhZFdyaXRlLkFsbCBXb3Jrc3BhY2UuUmVhZC5BbGwgV29ya3NwYWNlLlJlYWRXcml0ZS5BbGwiLCJzdWIiOiJkQzlDN1hSbnktdHcyWGc2QkRqYUZULWg3cmo1T05lN2VkOWV6dTZqWmQ4IiwidGlkIjoiMWMxNzY3MTItMTVmNy00MThjLTk1YWMtNDZmNmE2YTU5NWM1IiwidW5pcXVlX25hbWUiOiJoc2hlaWtoYWxpQGlnbG9vc29mdHdhcmUuY29tIiwidXBuIjoiaHNoZWlraGFsaUBpZ2xvb3NvZnR3YXJlLmNvbSIsInV0aSI6ImxhYUd6UWVKRDBxZFVTbDZtRjRmQUEiLCJ2ZXIiOiIxLjAifQ.prKqNPn75_CWdbLvsl5VVvuZAK-PEI2n1DlU4gFayt_eLPzllZUlEpVqIgVgTAzeccYEj5Z6vBKpMjXT7ftwCVjnKQitidGILehaEfrWiXX3xU4ZatPQ_TNc6Y6NzMyIQTWAbPkCHfpFnBlbAD0xp9Kl-bpAq_QXbl4yIa6_IQMRMwi5WdWd8WJLLdxKQTkWiKkGBBl-La3wgYrWfzBXMzBLhlfMk_vqsOyJdg1jOUEnUmScqxKh5DUR5DvoRtdeVxc2rDz1GWM8MTdhviB0CRub7bKeMA35rLzEui69L4o8gT_FuXLXqvVLDL9sq7OZNX8q3BL-VxLkq6GSOpnkcg"
    };


    try {
        const response = await client.put(`/${path}${filter}`, payload,{
            headers: {
                'x-ms-date': new Date().toUTCString(),
                'Accept-Charset': 'UTF-8',
                'Authorization': sharedKey.GenerateSharedKeyLite(path, "PUT"),
                Accept: 'application/json;odata=nometadata',
                'x-ms-version': '2015-12-11',
                'Content-Type': 'application/json',
                'If-Match': '*'
            }
        });

        console.log(response);
    } catch (err) {
        console.log(err.response.data);
    }
}

async function deleteEntity(path, filter, partitionKey, rowKey) {
    try {
        const response = await client.delete(`/${path}${filter}`, {
            headers: {
                'x-ms-date': new Date().toUTCString(),
                'Accept-Charset': 'UTF-8',
                'Authorization': sharedKey.GenerateSharedKeyLite(path),
                Accept: 'application/json;odata=nometadata',
                'x-ms-version': '2015-12-11',
                'Content-Type': 'application/json',
                'If-Match': '*'
            }
        });

        console.log(response);
    } catch (err) {
        console.log(err.response.data);
    }
}

//Works
getTables("powerbiTableStorage()", "?$filter=PartitionKey%20eq%20'ABCDEFG1234567RANDOMPARTITIONKEY1111'%20and%20RowKey%20eq%20'ABCDEFG1234567'");


//Works
addEntityToTable('powerbiTableStorage()');


//does not work
updateEntity("powerbiTableStorage(PartitionKey='ABCDEFG1234567RANDOMPARTITIONKEY1111',RowKey='ABCDEFG1234567')", "?$filter=PartitionKey%20eq%20'ABCDEFG1234567RANDOMPARTITIONKEY1111'%20and%20RowKey%20eq%20'ABCDEFG1234567'");


//does not work
deleteEntity("powerbiTableStorage(PartitionKey='ABCDEFG1234567RANDOMPARTITIONKEY1111',RowKey='ABCDEFG1234567')", "");

这是错误信息: { 'odata.error':{ 代码:'身份验证失败', 信息: { lang: 'en-US', value: '服务器验证请求失败。确保 Authorization 标头的值格式正确,包括签名。\n' + 'RequestId:d5684fff-8002-001c-0dd9-9fe2c5000000\n' + '时间:2019-11-20T19:31:28.7740552Z' } } }

有人可以帮我吗?

快速更新...

这是我运行 GET 请求时得到的结果

stringToSign: 格林威治标准时间 2019 年 11 月 21 日星期四 15:48:07 /igdevharuntest/表格

签名: SharedKeyLite igdevharuntest:PxpjkL+WwN7ZtHD1NXctjtMKSdWuAjNY3xS3jo7/n/Q=

{
  value: [
    { TableName: 'photowallCache' },
    { TableName: 'powerbiTableStorage' },
    { TableName: 'tblProjectStatus' }
  ]
}

共享密钥生成:

const Utf8 = require('crypto-js/enc-utf8');
const Base64 = require('crypto-js/enc-base64');
const hmacSHA256 = require('crypto-js/hmac-sha256');



class SharedKeyGenerator {
    constructor(storageAccount, tableName, storageKey) {

        this.storageAccountName = storageAccount;
        this.storageAccountKey = storageKey;
        this.tableName = tableName;

    }


    /**
     * generates a shared key lite for authorization
     *
     * @param path path of the resource
     * @returns {string} signed signature
     */
    GenerateSharedKeyLite(path) {
        const date = new Date().toUTCString();

        let stringToSign = date + '\n' + this._getCanonicalizedResource(path);
        const hash = hmacSHA256(Utf8.parse(stringToSign), Base64.parse(this.storageAccountKey));
        const signature = Base64.stringify(hash);

        console.log(stringToSign);
        console.log('SharedKeyLite ' + this.storageAccountName + ':' + signature);
        return 'SharedKeyLite ' + this.storageAccountName + ':' + signature;
    }

    _getCanonicalizedResource(path) {
        return `/${this.storageAccountName}/${path}`;
    }
}

module.exports = SharedKeyGenerator;


这是我执行 PUT 请求时得到的结果

stringToSign: 格林威治标准时间 2019 年 11 月 21 日星期四 15:48:07 /igdevharuntest/powerbiTableStorage(PartitionKey=NewPartitionKey12,RowKey=CompletelyNewKey12)

签名: SharedKeyLite igdevharuntest:dt14aw/McZet9JDiZDxnrZnwfMm8AfZZ7jNnTjVJ71A=

{
  'odata.error': {
    code: 'AuthenticationFailed',
    message: {
      lang: 'en-US',
      value: 'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.\n' +
        'RequestId:a9f6868a-c002-001b-6c83-a01440000000\n' +
        'Time:2019-11-21T15:48:07.3247287Z'
    }
  }
}

---- 更新 -----

所以我注意到,当我使用括号中的 PartitionKey 和 RowKey 值执行 GET 时,我收到了 AuthenticationFailed 错误。

例如: uri = GET:https://{azure_storage_account}.table.core.windows.net/TableStorageName(PartitionKey='PartitionKey',RowKey='RowKey')

回应是

{
  'odata.error': {
    code: 'AuthenticationFailed',
    message: {
      lang: 'en-US',
      value: 'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.\n' +
        'RequestId:a9f6868a-c002-001b-6c83-a01440000000\n' +
        'Time:2019-11-21T15:48:07.3247287Z'
    }
  }
}

如果我将括号保留为空,但添加一个过滤器作为查询参数,这样就可以正常工作,并且我可以获得表的特定实体。

例如: GET:https://{azure_storage_account}.table.core.windows.net/TableStorageNam()?$filter=PartitionKey eq 'PartitionKey' 和 RowKey eq 'RowKey'

回复是:

{
  value: [
    {
      PartitionKey: 'PartitionKey',
      RowKey: 'RowKey',
      Timestamp: '2019-11-20T18:16:24.5881171Z',
      expiresOn: '2019-10-16T17:27:36.046Z',
      accessToken: 'abcsd123456'
    }
  ]
}

我在想我构建规范化资源的方式对于括号内的值可能不正确?我不知道..我现在被困住了。我所有的标头值都是正确的,所以我认为这不是问题。

【问题讨论】:

  • 注意我删除了 GenerateSharedKeyLite 的第二个参数.. 没有做任何事情..
  • 您可以编辑您的问题并包含_getCanonicalizedResource 方法的代码吗?另外,请附上详细的错误信息。
  • @GauravMantri 完成,我已经更新了问题
  • @GauravMantri 了解我的更新吗?当我对 PUT 请求使用我的签名签名时,我仍然收到一条错误消息

标签: javascript node.js azure


【解决方案1】:

更新:

这是由于在调用更新/删除函数时,partitionKey 和 RowKey 之间缺少一个空白

调用删除函数时,应该改变

`deleteEntity("powerbiTableStorage(PartitionKey='ABCDEFG1234567RANDOMPARTITIONKEY1111',RowKey='ABCDEFG1234567')", "");`

`deleteEntity("powerbiTableStorage(PartitionKey='ABCDEFG1234567RANDOMPARTITIONKEY1111', RowKey='ABCDEFG1234567')", "");`

调用更新函数时,应该改变

updateEntity("powerbiTableStorage(PartitionKey='ABCDEFG1234567RANDOMPARTITIONKEY1111',RowKey='ABCDEFG1234567')", "?$filter=PartitionKey%20eq%20'ABCDEFG1234567RANDOMPARTITIONKEY1111'%20and%20RowKey%20eq%20'ABCDEFG1234567'");

updateEntity("powerbiTableStorage(PartitionKey='ABCDEFG1234567RANDOMPARTITIONKEY1111', RowKey='ABCDEFG1234567')", "");

对于删除:

const request = require("request");
const crypto = require("crypto");
const url = require('url')

var accountName = "xxx";
var accountKey = "xxx";
var tableName = "test22";
var pk = "r1";
var rk = "s5";

const encodedUriPath = tableName + '(PartitionKey=' + '\'' + pk + '\'' + ', ' + 'RowKey=' + '\'' + rk + '\'' + ')';
console.log(encodedUriPath)
const endpoint = "https://" + accountName + ".table.core.windows.net/" + encodedUriPath;
const parsedUrl = url.parse(endpoint);
const timestamp = (new Date()).toUTCString();

console.log(url);
console.log(timestamp);
const stringToSign = timestamp + '\n/' +  accountName + parsedUrl.path;
console.log('--------------------------------------');
console.log(stringToSign);

const hmac = crypto.createHmac('sha256', new Buffer(accountKey, 'base64'))
                  .update(stringToSign, 'utf-8')
                  .digest('base64');
console.log('--------------------------------------');
console.log(hmac);             

request.delete({
  'headers': {
    'Authorization': 'SharedKeyLite ' + accountName + ':' + hmac,
    'x-ms-date': timestamp,
    'x-ms-version': '2016-05-31',
    'Content-Type': 'application/json',
    'If-Match': '*'
  },
  'url': endpoint,
  'json': true
  }, function (err, result) {
  if (err) {
    console.log('inside delete err', JSON.stringify(err));

  } else {
      console.log(JSON.stringify(result));
  }
});

测试结果:


Update EntityDelete Entity rest api 需要标头If-Match(参考Request Headers)。否则会抛出错误“AuthenticationFailed, xxxx”。

在您的updateEntity 方法中,在headers 部分添加'If-Match': '*',例如:

const response = await client.put(`/${tableName}(PartitionKey='${partitionKey}', RowKey='${rowKey}')`, payload,{
            headers: {
                'x-ms-date': new Date().toUTCString(),
                'Authorization': sharedKey.GenerateSharedKeyLite(tableName),
                Accept: 'application/json;odata=nometadata',
                'x-ms-version': '2015-12-11',
                'Content-Type': 'application/json',
                'If-Match': '*'
            }

【讨论】:

  • 所以我确保我有更新的 If-Match 标头,但我仍然得到 AuthenticationFailed。我的签名有问题..因为微软返回一个错误,说我有一个缺少的标题字段,如果它在那里,除非我错了......无论如何这对我不起作用。
  • @ibnawfi,我刚刚尝试了删除操作,它有效。稍后我会尝试进行更新操作。
  • @ibnawfi,这只是删除操作,如果你需要,我贴出来。
  • 好吧,我尝试了删除操作,但这对我也不起作用..我还需要 PUT 操作..
  • @ibnawfi,好的,我会在几分钟后先用删除操作更新我的帖子。您能否发布您完成的代码(以及您正在使用的软件包)以进行更新/删除?所以我可以按照你的代码进行测试。
【解决方案2】:

您提到您的请求适用于 GETPOST 请求,但不适用于 PUTDELETE 请求。我相信您需要在计算 stringToSign 的位置包含 (PartitionKey='${partitionKey}', RowKey='${rowKey}'),因为这是 URI 的一部分。

GenerateSharedKeyLite(tableName, partitionKey, rowKey) {
    const date = new Date().toUTCString();
    let stringToSign = date + '\n' + this._getCanonicalizedResource() + '/' + tableName + '(PartitionKey=' + partitionKey + ', RowKey=' + rowKey + ')';
    const hash = hmacSHA256(Utf8.parse(stringToSign), Base64.parse(this.storageAccountKey));
    const signature = Base64.stringify(hash);


    console.log(stringToSign);
    // console.log returns this /igdevharuntest/powerbiTableStorage


    return'SharedKeyLite ' + this.storageAccountName + ':' + signature;
}

请试一试。

【讨论】:

  • 嗯...这很奇怪。你还在收到 403 错误吗?您能否编辑您的问题并在帖子底部添加更新后的代码。
  • 嘿,我已经添加了更新.. 我添加的只是:让 stringToSign = date + '\n' + this._getCanonicalizedResource() + '/' + tableName + '(PartitionKey=' + partitionKey + ',RowKey=' + rowKey + ')';当我去更新实体时,我得到一个 403
  • 我注意到您在请求正文中传递了全新的 PartitionKey 和 RowKey。您是否尝试通过更新操作更改这些值?
  • 是的,但如果这不是必需的,那么我可以删除它我只是在测试我是否可以更新所有字段
  • 您无法更改这两个属性,但您需要将它们与原始值一起包含在请求正文中。您可以尝试进行此更改吗?
猜你喜欢
  • 2020-03-22
  • 2010-10-14
  • 2023-02-17
  • 2015-11-08
  • 1970-01-01
  • 1970-01-01
  • 2020-06-06
  • 1970-01-01
  • 2018-03-22
相关资源
最近更新 更多