通过 Azure APIM 发送请求调用 Cosmos DB Api 时出现 401 Unauthorized

401 Unauthorized when calling Cosmos DB Api via Azure APIM send-request

当通过 APIM send-request 频繁调用 CosmosDB API 时,有时我会收到 401 Unauthorized,有时会收到 200,这正是我所期望的。我尝试生成相同的请求并使用 Postman 频繁发送它,并且始终响应为 200。

这里是 APIM 策略代码片段:

    <set-variable name="currentDate" value="@(DateTime.UtcNow.ToString("r"))" />
    <set-variable name="userMetadataCosmosDBKey" value="{{UserMetadataCosmosDBKey}}" />
    <set-variable name="userMetadataCosmosDBUri" value="{{UserMetadataCosmosDBUri}}" />
    <set-variable name="userMetadataResourceId" value="{{UserMetadataResourceId}}" />
    <set-variable name="token" value="@{
      var verb = "POST";
      var resourceType = "docs";
      var resourceId = (string)context.Variables["userMetadataResourceId"];
      var date = (string)context.Variables["currentDate"];
      var key = (string)context.Variables["userMetadataCosmosDBKey"];
      var keyType = "master";
      var tokenVersion = "1.0";
      var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
      verb = verb ?? "";
      resourceType = resourceType ?? "";
      resourceId = resourceId ?? "";
      string payLoad = string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n",
      verb.ToLowerInvariant(),
      resourceType.ToLowerInvariant(),
      resourceId,
      date.ToLowerInvariant(),
      "");
      byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
      string signature = Convert.ToBase64String(hashPayLoad);
      return String.Format("type={0}&ver={1}&sig={2}",
        keyType,
        tokenVersion,
        signature);        
     }" />
    <send-request mode="new" response-variable-name="userMetadataResponse" timeout="20" ignore-error="false">
        <set-url>@(new Uri(string.Format("{0}{1}/docs",(string)context.Variables["userMetadataCosmosDBUri"],(string)context.Variables["userMetadataResourceId"])).ToString())</set-url>
        <set-method>POST</set-method>
        <set-header name="x-ms-documentdb-isquery" exists-action="override">
            <value>true</value>
        </set-header>
        <set-header name="Content-Type" exists-action="override">
            <value>application/query+json</value>
        </set-header>
        <set-header name="x-ms-version" exists-action="override">
            <value>2017-02-22</value>
        </set-header>
        <set-header name="x-ms-date" exists-action="override">
            <value>@((string)context.Variables["currentDate"])</value>
        </set-header>
        <set-header name="x-ms-query-enable-crosspartition" exists-action="override">
            <value>true</value>
        </set-header>
        <set-header name="Authorization" exists-action="override">
            <value>@((string)context.Variables["token"])</value>
        </set-header>
        <set-body>@{  
                        return new JObject(  
                            new JProperty("query","SELECT * FROM c WHERE c.APIMUserId=@apiUserId"),  
                            new JProperty("parameters", 
                                new JArray(
                                    new JObject(  
                                        new JProperty("name","@apiUserId"),  
                                        new JProperty("value", context.User.Id)
                                        )
                                    )
                                )
                            ).ToString();  
                    }</set-body>
    </send-request>
    <!-- Store response body in userMetadata variable -->
    <set-variable name="userMetadata" value="
      @{
         var response = ((IResponse)context.Variables["userMetadataResponse"]).Body.As<JObject>(true);                                                                                                                                                                                                           
         var metadata = response["_count"].ToString() == "1" ? response["Documents"][0] : new JObject();                                                                                                                                                                                                                                                
         return metadata;                                                                                                                                                                                                                                                
       }" />

有没有想过为什么我有时会收到 401?

通过将 System.Uri.EscapeDataString() 添加到返回的令牌使其工作:

System.Uri.EscapeDataString(String.Format("type={0}&ver={1}&sig={2}",
                                    keyType,
                                    tokenVersion,
                                    signature));