Microsoft.WindowsAzure.Storage 9.3.2.0 偶发403 Forbidden问题咨询
使用Microsoft.WindowsAzure.Storage(Windows平台Azure存储SDK)9.3.2.0版本实现Blob上传功能,对应C#代码如下:
private class FileBlock { internal string Id { get; set; } internal byte[] Content { get; set; } } public static async Task<Uri> UploadFileToAzureStorageAsync(string azureStorageUri, string filePath) { var bytes = File.ReadAllBytes(filePath); var cloudBlockBlob = new CloudBlockBlob(new Uri(azureStorageUri)); var blocks = new HashSet<string>(); `enter code here` try { foreach (var block in GetFileBlocks(bytes)) { cloudBlockBlob.PutBlock(block.Id, new MemoryStream(block.Content, true), null); blocks.Add(block.Id); } await cloudBlockBlob.PutBlockListAsync(blocks); } catch (Exception ex) { Logging.logger.Error(ex); } return cloudBlockBlob.Uri; } private static IEnumerable<FileBlock> GetFileBlocks(byte[] fileContent) { if (fileContent.Length == 0) return new HashSet<FileBlock>(); var maxBlockSize = 4 * 1024 * 1024; var hashSet = new HashSet<FileBlock>(); var blockId = 0; var index = 0; var currentBlockSize = maxBlockSize; while (currentBlockSize == maxBlockSize) { if ((index + currentBlockSize) > fileContent.Length) currentBlockSize = fileContent.Length - index; var chunk = new byte[currentBlockSize]; Array.Copy(fileContent, index, chunk, 0, currentBlockSize); hashSet.Add(new FileBlock { Content = chunk, Id = Convert.ToBase64String(BitConverter.GetBytes(blockId)) }); index += currentBlockSize; blockId++; } return hashSet; }
该功能仅在少量包上传时偶发报错,重新触发发布即可上传成功,调整运行机器时区为UTC后问题仍未解决,捕获异常信息如下:
Microsoft.WindowsAzure.Storage.StorageException: The remote server returned an error: (403) Forbidden.
---> System.Net.WebException: The remote server returned an error: (403) Forbidden.
StatusMessage:Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
ErrorCode:AuthenticationFailed
ErrorMessage:Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
- 客户端与服务端时间偏差过大:Azure存储服务要求请求签名的生成时间和服务端时间偏差不能超过15分钟,仅修改时区为UTC没用,如果机器本地时间和NTP服务器同步不稳定,偶发时间跳变超过阈值就会触发鉴权失败
- SAS令牌过期:如果传入的
azureStorageUri是带SAS令牌的地址,若SAS有效期设置过短,大文件分块上传耗时超过SAS有效期时,就会出现偶发403,小文件上传快不会触发,重试时新生成的SAS有效就能上传成功 - 代码逻辑缺陷:代码中
PutBlock使用同步方法,未配置重试策略,网络抖动导致单块上传耗时过长时,后续请求的签名时间超出有效窗口也会触发鉴权失败,且同步异步混用可能导致意料之外的时间延迟 - 旧版SDK Bug:Microsoft.WindowsAzure.Storage 9.x版本已经停止维护,存在已知的偶发签名计算错误的问题,高并发场景下更容易出现
- 检查运行机器的NTP时间同步配置,确保机器时间和标准时间偏差控制在5分钟以内,仅调整时区无法解决时间本身不准的问题
- 若使用SAS令牌鉴权,将SAS的有效期设置为预估最大上传耗时的2倍以上,避免大文件上传过程中SAS过期
- 优化代码逻辑:将同步
PutBlock替换为异步PutBlockAsync并添加await,同时配置请求重试策略,示例配置参考:
var options = new BlobRequestOptions() { RetryPolicy = new ExponentialRetry(TimeSpan.FromSeconds(2), 5) }; cloudBlockBlob.ServiceClient.DefaultRequestOptions = options;
- 升级存储SDK到官方推荐的Azure.Storage.Blobs新版本,旧版SDK的已知签名问题在新版中已修复
- 必要时开启Azure存储账户的诊断日志,对比失败请求的客户端生成时间和服务端返回的
x-ms-date字段,可快速定位是否为时间偏差导致的问题
内容的提问来源于stack exchange,提问作者Sangamesh Gouri

