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

使用Shared Access Key上传Azure Blob报403禁止错误求助

Azure Blob Storage 上传PDF时403 Forbidden错误排查与修复

问题描述

我正在使用Shared Access Key (SAS)将PDF文件上传至Azure Blob Storage容器,此前代码运行正常,突然开始抛出403 - Forbidden异常。响应状态消息:

Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature

我已经尝试刷新了共享访问密钥,但问题仍未解决。代码运行在.NET Framework 4.6.2控制台应用中,核心上传方法及签名生成代码如下:

原代码片段

public string AzureStorageAccountName { get; set; }
public string AzureStorageAccessKey { get; set; }
public string X_MS_VERSION { get { return "2017-04-17"; } }
public string X_MS_CLIENT_REQUEST_ID { get { return _x_ms_client_request_id; } }
public string BaseURI { get { return string.Format("https://{0}.blob.core.windows.net/", AzureStorageAccountName); } }
private bool UploadBlobToStorageContainer(string filePath, string targetFolderPath, string containerName) {
    bool isUploaded = false;
    try {
        FileInfo fileInfo = new FileInfo(filePath);
        long contentLength = fileInfo.Length;
        long range = contentLength - 1;
        string method = "PUT";
        string contentType = "application/pdf";
        string blobName = fileInfo.Name;
        string blobType = "BlockBlob";
        string dateString = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
        string blobURI = BaseURI + containerName + "/" + blobName;
        string xmsHeader = $"x-ms-blob-type:{blobType}\nx-ms-date:{dateString}\nx-ms-version:{X_MS_VERSION}";
        string resHeader = $"/{AzureStorageAccountName}/{containerName}/{blobName}";
        if (!string.IsNullOrWhiteSpace(targetFolderPath)) {
            blobName = targetFolderPath + "/" + fileInfo.Name;
        }
        if (WebRequest.Create(blobURI) is HttpWebRequest request) {
            request.Method = method;
            request.ContentLength = contentLength;
            request.Headers.Add("x-ms-blob-type", blobType);
            request.Headers.Add("x-ms-date", dateString);
            request.Headers.Add("x-ms-version", X_MS_VERSION);
            request.Headers.Add("Authorization", GetAuthorizationHeader(method, xmsHeader, resHeader, contentType, contentLength));
            using (Stream requestStream = request.GetRequestStream()) {
                byte[] fileContents = null;
                using (FileStream fs = fileInfo.OpenRead()) {
                    fileContents = new byte[fs.Length];
                    fs.Read(fileContents, 0, fileContents.Length);
                    fs.Close();
                }
                requestStream.Write(fileContents, 0, fileContents.Length);
            }
            if (request.GetResponse() is HttpWebResponse response) {
                if (response.StatusCode == HttpStatusCode.Created) {
                    isUploaded = true;
                } else {
                    isUploaded = false;
                }
            }
        }
    } catch (Exception ex) {
        if (ex is WebException wex) {
            StringBuilder sb = new StringBuilder();
            if (wex.Response is HttpWebResponse exr) {
                sb.Append("StatusCode: " + exr.StatusCode + " - ");
                sb.Append("Description: " + exr.StatusDescription + " - ");
            }
            sb.Append("ErrorStatus: " + wex.Status);
            Log.LogMessage(LogLevel.ERROR, "AzureBlobApi: UploadBlobToContainer: File upload failed. Reason: " + sb.ToString());
        }
        Log.LogException(ex);
    }
    return isUploaded;
}
private string GetAuthorizationHeader(string method, string xmsHeader, string resHeader, string contentType, long contentLength) {
    string strToSign = $"{method}\n\n\n\n{contentLength}\n\n\n\n\n\n\n\n{xmsHeader}\n{resHeader}";
    string signatureString = GetHashedString(strToSign, AzureStorageAccessKey);
    string authorizationHeader = string.Format(
        CultureInfo.InvariantCulture, "{0} {1}:{2}", "SharedKey", AzureStorageAccountName, signatureString);
    return authorizationHeader;
}
private string GetHashedString(string signingString, string accessKey) {
    string encString = "";
    try {
        byte[] unicodeKey = Convert.FromBase64String(accessKey);
        using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey)) {
            byte[] dataToHmac = Encoding.UTF8.GetBytes(signingString);
            encString = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
        }
    } catch (Exception ex) {
        Log.LogMessage(LogLevel.ERROR, $"AzureBlobApi: GetHashedString: Exception getting hash string {ex.Message}");
    }
    return encString;
}

请求头信息

PUT 518262
x-ms-blob-type:BlockBlob
x-ms-date:Sun, 30 Aug 2020 08:43:31 GMT
x-ms-version:2017-04-17
/mystorage/documentcontainer/cricket/test document.pdf

问题排查与修复

我帮你找到了代码里的3个关键问题,这些是导致签名验证失败(403 Forbidden)的核心原因:

1. 请求URI与签名资源路径不匹配

你先基于原始blobName构建了blobURI和resHeader,但之后如果targetFolderPath不为空,修改了blobName却没有同步更新blobURI和resHeader。这就导致实际请求的Blob路径(包含文件夹)和签名中使用的路径(不包含文件夹)不一致,Azure验证签名时自然会失败。

2. 签名字符串缺少Content-Type字段

根据Azure SharedKey签名规则,Content-Type是必须包含在签名字符串中的字段,但你的strToSign跳过了这个位置,直接用空行代替,导致签名格式不符合要求。

3. 文件读取的潜在隐患

你使用fs.Read(fileContents, 0, fileContents.Length)读取文件内容,但Read方法不一定会一次性读取全部字节,可能导致上传的文件不完整,虽然这不是403的直接原因,但也是需要修复的问题。

修复后的代码

下面是修正后的核心代码部分:

private bool UploadBlobToStorageContainer(string filePath, string targetFolderPath, string containerName) {
    bool isUploaded = false;
    try {
        FileInfo fileInfo = new FileInfo(filePath);
        long contentLength = fileInfo.Length;
        string method = "PUT";
        string contentType = "application/pdf";
        string blobName = fileInfo.Name;
        string blobType = "BlockBlob";
        string dateString = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);

        // 先处理blobName的文件夹拼接,再构建URI和资源路径
        if (!string.IsNullOrWhiteSpace(targetFolderPath)) {
            blobName = $"{targetFolderPath.TrimEnd('/')}/{fileInfo.Name}";
        }
        string blobURI = $"{BaseURI}{containerName}/{Uri.EscapeDataString(blobName)}";
        string resHeader = $"/{AzureStorageAccountName}/{containerName}/{blobName}";
        string xmsHeader = $"x-ms-blob-type:{blobType}\nx-ms-date:{dateString}\nx-ms-version:{X_MS_VERSION}";

        if (WebRequest.Create(blobURI) is HttpWebRequest request) {
            request.Method = method;
            request.ContentLength = contentLength;
            request.ContentType = contentType; // 设置Content-Type请求头
            request.Headers.Add("x-ms-blob-type", blobType);
            request.Headers.Add("x-ms-date", dateString);
            request.Headers.Add("x-ms-version", X_MS_VERSION);
            request.Headers.Add("Authorization", GetAuthorizationHeader(method, xmsHeader, resHeader, contentType, contentLength));

            using (Stream requestStream = request.GetRequestStream()) {
                // 使用CopyTo确保完整读取文件内容
                using (FileStream fs = fileInfo.OpenRead()) {
                    fs.CopyTo(requestStream);
                }
            }

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
                isUploaded = response.StatusCode == HttpStatusCode.Created;
            }
        }
    } catch (Exception ex) {
        // 异常处理代码保持不变
        if (ex is WebException wex) {
            StringBuilder sb = new StringBuilder();
            if (wex.Response is HttpWebResponse exr) {
                sb.Append("StatusCode: " + exr.StatusCode + " - ");
                sb.Append("Description: " + exr.StatusDescription + " - ");
            }
            sb.Append("ErrorStatus: " + wex.Status);
            Log.LogMessage(LogLevel.ERROR, "AzureBlobApi: UploadBlobToContainer: File upload failed. Reason: " + sb.ToString());
        }
        Log.LogException(ex);
    }
    return isUploaded;
}

private string GetAuthorizationHeader(string method, string xmsHeader, string resHeader, string contentType, long contentLength) {
    // 按照Azure签名规则,正确填充各个字段位置,包括Content-Type
    string strToSign = $"{method}\n" +
                       "\n" + // Content-Encoding
                       "\n" + // Content-Language
                       $"{contentLength}\n" + // Content-Length
                       "\n" + // Content-MD5
                       $"{contentType}\n" + // Content-Type
                       "\n" + // Date
                       "\n" + // If-Modified-Since
                       "\n" + // If-Match
                       "\n" + // If-None-Match
                       "\n" + // If-Unmodified-Since
                       "\n" + // Range
                       $"{xmsHeader}\n" +
                       $"{resHeader}";
    string signatureString = GetHashedString(strToSign, AzureStorageAccessKey);
    string authorizationHeader = string.Format(
        CultureInfo.InvariantCulture, "{0} {1}:{2}", "SharedKey", AzureStorageAccountName, signatureString);
    return authorizationHeader;
}

额外说明

  • 构建blobURI时使用Uri.EscapeDataString(blobName)处理文件名中的特殊字符(比如空格),避免URI格式错误。
  • 使用FileStream.CopyTo代替手动读取字节数组,确保文件内容完整上传。
  • 确保resHeader中的blobName和实际请求的blobName完全一致,包括文件夹路径。

内容的提问来源于stack exchange,提问作者Raghunathan Sundarrajan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 00:12:53