使用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
相关产品推荐
相关产品推荐

