Исключение операции управления API Azure для сохранения данных в хранилище BLOB-объектов Azure завершается неудачно в PowerShell-Script, но не в Postman или DeveloperPortal - PullRequest
1 голос
/ 22 октября 2019

В Azure API Management разные результаты для разных клиентов.

Это политика управления API для хранения JSON-документа в хранилище BLOB-объектов Azure:

        <base />
        <!-- ########## put to storage ########## -->
        <set-variable name="resource" value="@{
                            string prefix = "/payloads/" + context.Request.MatchedParameters["business-object"] + "/";
                            string fileName = string.empty;
                            return prefix + fileName;
                            }" />
        <set-variable name="storageUrl" value="{{STORAGE_URL}}" />
        <set-variable name="blobUrl" value="@((string)context.Variables["storageUrl"] + (string)context.Variables["resource"])" />
        <set-variable name="storageKey" value="{{STORAGE_KEY}}" />
        <set-variable name="storageAccountName" value="@(context.Variables.GetValueOrDefault<string>("storageUrl").Split('.')[0].Split('/')[2])" />
        <set-variable name="date" value="@(DateTime.UtcNow.ToString("R"))" />
        <set-variable name="version" value="2018-03-28" />
        <trace source="keyInput">@{
                        string body = context.Request.Body.As<string>(preserveContent: true);
                        string contentType = "text/plain";
                        string contentLength = context.Request.Headers["Content-Length"][0];
                        var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(context.Variables.GetValueOrDefault<string>("storageKey")) };
                        var payLoad = string.Format("{0}\n\n\n{1}\n\n{2}\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:{3}\nx-ms-version:{4}\n{5}", 
                            "PUT", 
                            contentLength,
                            contentType,
                            context.Variables["date"],
                            context.Variables["version"],
                            "/" + context.Variables.GetValueOrDefault<string>("storageAccountName") + context.Variables.GetValueOrDefault<string>("resource"));
                        return payLoad;
                    }</trace>
        <send-request mode="new" response-variable-name="putStorageRequest" timeout="5" ignore-error="true">
            <set-url>@((string)context.Variables["blobUrl"])</set-url>
            <set-method>PUT</set-method>
            <set-header name="x-ms-date" exists-action="override">
                <value>@((string) context.Variables["date"] )</value>
            </set-header>
            <set-header name="x-ms-version" exists-action="override">
                <value>@((string) context.Variables["version"] )</value>
            </set-header>
            <set-header name="x-ms-blob-type" exists-action="override">
                <value>BlockBlob</value>
            </set-header>
            <set-header name="Content-Type" exists-action="override">
                <value>application/json</value>
            </set-header>
            <set-header name="Authorization" exists-action="override">
                <value>@{
                        string body = context.Request.Body.As<string>(preserveContent: true);
                        string contentType = "application/json";
                        string contentLength = context.Request.Headers["Content-Length"][0];
                        var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(context.Variables.GetValueOrDefault<string>("storageKey")) };
                        var payLoad = string.Format("{0}\n\n\n{1}\n\n{2}\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:{3}\nx-ms-version:{4}\n{5}", 
                            "PUT", 
                            contentLength,
                            contentType,
                            context.Variables["date"],
                            context.Variables["version"],
                            "/" + context.Variables.GetValueOrDefault<string>("storageAccountName") + context.Variables.GetValueOrDefault<string>("resource"));
                        return "SharedKey "+ context.Variables.GetValueOrDefault<string>("storageAccountName") + ":" + Convert.ToBase64String(hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad)));
                    }</value>
            </set-header>
            <set-body>@( context.Request.Body.As<string>(true) )</set-body>
        </send-request>
        <choose>
            <when condition="@(context.Variables["putStorageRequest"] == null)">
                <return-response>
                    <set-status code="500" reason="Storage failure" />
                    <set-body />
                </return-response>
            </when>
            <when condition="@(((IResponse)context.Variables["putStorageRequest"]).StatusCode != 201)">
                <return-response>
                    <set-status code="500" reason="Storage failure" />
                    <set-body>@(((IResponse)context.Variables["putStorageRequest"]).Body.As<string>())</set-body>
                </return-response>
            </when>
        </choose>
    </inbound>

Ocp-Apim-Subscription-Key используется в качестве HTTP-заголовка для проверки подлинности.

При его выполнении на портале API Management Developer Portal работает, как и ожидалось, и документ сохраняется в хранилище BLOB-объектов Azure.

При выполнении PowerShell-Script происходит сбой:

Invoke-RestMethod -Method POST -Uri $url -Headers $authHeaders -Body $body -ContentType "application/json"

Исключение:

code: 403
reason: "Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature."

Проблема заключается в том, что длина содержимого изменяется во время входящеготечь. Ниже приведена выдержка из OCP-Trace:

{
  "traceEntries": {
    "inbound": [
      {
        "source": "api-inspector",
        "timestamp": "2019-10-22T13:52:47.4545895Z",
        "elapsed": "00:00:00.0019930",
        "data": {
          "request": {
            "method": "POST",
            "url": "https://lorem.ipsum/private/api/store",
            "headers": [
              {
                "name": "Ocp-Apim-Subscription-Key",
                "value": "secret"
              },
              {
                "name": "Connection",
                "value": "Keep-Alive"
              },
              {
                "name": "Content-Length",
                "value": "13782"
              },
              {
                "name": "Content-Type",
                "value": "application/json"
              },
              {
                "name": "Host",
                "value": "lorem.ipsum"
              },
              {
                "name": "User-Agent",
                "value": "Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.14393.3053"
              }
            ]
          }
        }
      },
      {
        "source": "keyInput",
        "timestamp": "2019-10-22T13:52:47.4545895Z",
        "elapsed": "00:00:00.0036425",
        "data": "PUT 13782 text/plain x-ms-blob-type:BlockBlob x-ms-date:Tue, 22 Oct 2019 13:52:47 GMT x-ms-version:2018-03-28 --CUTTED--"
      },
      {
        source: "send-request",
        timestamp: "2019-10-22T13:52:47.4545895Z",
        elapsed: "00:00:00.0040858",
        data: {
          message: "Request is being forwarded to the backend service. Timeout set to 5 seconds",
          request: {
            method: "PUT",
            url: "https://lorem.ipsum.blob.core.windows.net/payloads/stuff/b812a1b4-decd-45a1-bf00-f7792fb3789a",
            headers: [
              {
                name: "Content-Length",
                value: 13784
              }
            ]
          }
        }
      },
      {
        source: "send-request",
        timestamp: "2019-10-22T13:52:47.5639587Z",
        elapsed: "00:00:00.1123550",
        data: {
          response: {
            status: {
              code: 403,
              reason: "Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature."
            }
          }
        }
      }
    ]
  }
}

Длина содержимого не изменяется, если API вызывается с почтальоном.

Почему изменяется длина содержимого ивызывает у меня проблему аутентификации?

--- ОБНОВЛЕНИЕ ---

Это также зависит от содержимого:

- против

Хорошо:

{
"value": "te-st"
}

Плохо:

"value": "te–st"
}

Другая вещь - это кодировка файла JSON-документа, используемого в PowerShell.
Плохо работает как ANSI
Плохо не работает как UTF-8

Это имеет смысл, этотакже задокументировано:
https://docs.microsoft.com/en-us/powershell/scripting/components/vscode/understanding-file-encoding?view=powershell-6#common-causes-of-encoding-issues

Эта проблема возникает из-за того, что VSCode кодирует символ - в UTF-8 как байты 0xE2 0x80 0x93. Когда эти байты декодируются как Windows-1252, они интерпретируются как символы.

Некоторые странные последовательности символов, которые вы можете увидеть, включают:

- вместо -
- вместо -

Но это не объясняет, почему Content-Length изменяется в API-менеджменте.
И как мне справиться с неправильным кодированием в API-менеджменте?

...