You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用HttpClient更新Azure Table Storage实体时遇Error 403认证失败

Azure Table Storage 更新实体时403认证错误排查与解决

错误代码 - 403
"服务器无法验证请求。请确保Authorization标头的值(包括签名)格式正确。"

请求无法通过认证,但已添加了几乎所有的认证标头,resourcePath = TableName,以下是问题代码及解决方案:

问题代码

public async Task<string> UpdateEntityAsync(string department, string id, EmployeeDetails employee)
{
        string uri = @"https://" + storageAccount + ".table.core.windows.net/" + resourcePath + "(PartitionKey='" + department + "',RowKey='" + id + "')";
        string body = JsonConvert.SerializeObject(employee);
        //body = string.Format(body, Guid.NewGuid());

        var request = _httpClientFactory.CreateClient();
        string formatedTime = DateTime.UtcNow.ToString("R");
        request.DefaultRequestHeaders.Add("x-ms-date", formatedTime);

        if (request.DefaultRequestHeaders.Contains("x-ms-version"))
            request.DefaultRequestHeaders.Remove("x-ms-version");

        request.DefaultRequestHeaders.Add("x-ms-version", "2015-12-11");

        if (request.DefaultRequestHeaders.Contains("DataServiceVersion"))
            request.DefaultRequestHeaders.Remove("DataServiceVersion");
        request.DefaultRequestHeaders.Add("DataServiceVersion", "3.0;NetFx");

        if (request.DefaultRequestHeaders.Contains("MaxDataServiceVersion"))
            request.DefaultRequestHeaders.Remove("MaxDataServiceVersion");
        request.DefaultRequestHeaders.Add("MaxDataServiceVersion", "3.0;NetFx");

        if (request.DefaultRequestHeaders.Contains("If-Match"))
            request.DefaultRequestHeaders.Remove("If-Match");
        request.DefaultRequestHeaders.Add("If-Match", "*");

        request.DefaultRequestHeaders.Accept.Clear();
        request.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        //Adding the Authorization header to the request
        string authorization = GetSignedStringPut("PUT", formatedTime, resourcePath, storageAccount, accessKey, department, id);
        request.DefaultRequestHeaders.Add("Authorization", authorization);

        request.DefaultRequestHeaders.TryAddWithoutValidation("Content-Length", body.Length.ToString());
        var stringContent = new StringContent(body);
        stringContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
        HttpResponseMessage messageResult = await request.PutAsync(uri, stringContent);
        if (messageResult.IsSuccessStatusCode)
        {
            return messageResult.ToString();
        }
        else
        {
            return "null";
        }
    }

    public static string GetSignedStringPut(string httpMethod, string time, string urlPath, string account, string key, string department, string id)
    {
        String contentMD5 = String.Empty;
        String contentType = "application/json";
        String canonicalizedResource = String.Format("/{0}/{1}", account, urlPath) + "(partitionkey='" + department + "',rowkey='" + id + "')";
        //stringToSign format for SharedKey
        String stringToSign = String.Format(
              "{0}\n{1}\n{2}\n{3}\n{4}",
              httpMethod,
              contentMD5,
              contentType,
              time,
              canonicalizedResource);
        //stringToSign format for SharedKeyLite
        //stringToSign = String.Format("{0}\n{1}", time, canonicalizedResource);
        string signedKey = SignThisPut(stringToSign, key, account);
        return signedKey;
    }

    private static String SignThisPut(String canonicalizedString, string Key, string Account)
    {
        String signature = string.Empty;
        byte[] unicodeKey = Convert.FromBase64String(Key)
;
        using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey))
        {
            Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(canonicalizedString);
            signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
        }

        String authorizationHeader = String.Format(
              CultureInfo.InvariantCulture,
              "{0} {1}:{2}",
              "SharedKey",
              Account,
              signature);

        return authorizationHeader;
    }
}
}

核心问题与修复方案

1. 规范化资源路径的大小写错误

Azure Table Storage对PartitionKey和RowKey的大小写严格要求,原代码中canonicalizedResource里的键名是小写的partitionkey和rowkey,必须改为大写开头:

String canonicalizedResource = String.Format("/{0}/{1}", account, urlPath) + "(PartitionKey='" + department + "',RowKey='" + id + "')";

2. 缺失Content-MD5的计算与传递

PUT请求带body时,Azure要求必须计算body的MD5哈希值,同时在请求头和签名字符串中传入该值:

  • 在UpdateEntityAsync中计算MD5:
string body = JsonConvert.SerializeObject(employee);
string contentMD5 = string.Empty;
using (var md5 = MD5.Create())
{
    byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
    byte[] hashBytes = md5.ComputeHash(bodyBytes);
    contentMD5 = Convert.ToBase64String(hashBytes);
}
// 添加到请求头
request.DefaultRequestHeaders.Add("Content-MD5", contentMD5);
  • 修改GetSignedStringPut方法,接收并传入contentMD5:
public static string GetSignedStringPut(string httpMethod, string time, string urlPath, string account, string key, string department, string id, string contentMD5)
{
    String contentType = "application/json";
    String canonicalizedResource = String.Format("/{0}/{1}", account, urlPath) + "(PartitionKey='" + department + "',RowKey='" + id + "')";
    String stringToSign = String.Format(
          "{0}\n{1}\n{2}\n{3}\n{4}",
          httpMethod,
          contentMD5,
          contentType,
          time,
          canonicalizedResource);
    string signedKey = SignThisPut(stringToSign, key, account);
    return signedKey;
}
  • 调用时传入计算好的contentMD5:
string authorization = GetSignedStringPut("PUT", formatedTime, resourcePath, storageAccount, accessKey, department, id, contentMD5);

3. 移除手动设置的Content-Length

StringContent会自动处理Content-Length字段,手动添加可能导致值不匹配,直接删除以下代码:

// request.DefaultRequestHeaders.TryAddWithoutValidation("Content-Length", body.Length.ToString());

4. 确认时间同步

确保本地服务器UTC时间与标准时间差不超过15分钟,Azure会拒绝时间偏差过大的请求。

内容的提问来源于stack exchange,提问作者Kanchan Kausal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 15:39:37