繁体   English   中英

Azure BLOB 存储 REST API 上传文件抛出 403 - 禁止访问

[英]Azure BLOB Storage REST API Uploading a file throws 403 - Forbidden

我正在尝试使用共享访问密钥(SAS) 将 PDF 上传到 Azure Blob 存储容器。 代码运行良好,但突然开始抛出403 - Forbidden Exception

响应状态消息:

服务器无法验证请求。 确保正确形成授权 header 的值,包括签名。

如此链接中所述,403 的可能原因之一可能是共享访问密钥已过期,因此我刷新了密钥并尝试使用新密钥,但仍然没有成功。

我正在为控制台应用程序 (.NET Framework 4.6.2) 调用UploadBlobToStorageContainer方法

代码:

    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)
    {
        //Do NOT REMOVE THE \n. It is a request header placeholder
        /*
            GET\n //HTTP Verb
            \n    //Content-Encoding
            \n    //Content-Language
            \n    //Content-Length (empty string when zero)
            \n    //Content-MD5
            \n    //Content-Type
            \n    //Date
            \n    //If-Modified-Since 
            \n    //If-Match
            \n    //If-None-Match
            \n    //If-Unmodified-Since
            \n    //Range
            x-ms-date:Fri, 26 Jun 2015 23:39:12 GMT
            x-ms-version:2015-02-21 //CanonicalizedHeaders
            /myaccount/mycontainer\ncomp:metadata\nrestype:container\ntimeout: 20    //CanonicalizedResource
        */
        
        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

任何帮助将非常感激。

感谢 Raghunathan S

Http 403一般是认证失败,请确保SAS有足够的权限在指定容器中创建和写入blob,同时确保容器本身已经存在(因为创建容器需要额外的权限),更多细节SAS权限请参考创建服务SAS创建账号SAS。

验证 SAS 是否具有足够权限的一种简单方法是在您的代码中调用 Azure 存储客户端库的blockBlob.UploadFromFile() ,并查看它是否有效或是否也报告了相同的错误。

要在 DMLib 中使用容器 SAS:

CloudBlockBlob blob = new CloudBlockBlob(new Uri("https://<storage>.blob.core.windows.net/<container_name/testaaa?st=2020-02-15T08%3A30%3A00Z&se=2020-02-20T08%3A30%3A00Z&sp=rwdl&sv=2016-05-31&sr=c&sig=<Replaced>"));
TransferManager.UploadAsync(@"c:\Test.pdf", blob).Wait();

您的代码中有一些错误。

1.在GetAuthorizationHeader()方法中,请使用以下代码行代替您的代码:

string strToSign = $"{method}\n\n\n{contentLength}\n\n{contentType}\n\n\n\n\n\n\n{xmsHeader}\n{resHeader}";

2.在UploadBlobToStorageContainer()方法中,请将 contentType 添加到请求中,如下所示:

                request.Method = method;

                //add contentType here
                request.ContentType = contentType;
                request.ContentLength = contentLength;
                request.Headers.Add("x-ms-blob-type", blobType);

3.对于X_MS_VERSION,建议使用最新的2019-12-12版本,如下:

    public string X_MS_VERSION
    {
        get
        {
            return "2019-12-12";
        }
    }

最后,我发布了工作代码示例。 如果仍然有问题,请尝试下面的代码(注意,我假设示例中的 targetFolderPath 为空,如果要将文件上传到目录,请根据代码调整):

    //other code

    public string X_MS_VERSION
    {
        get
        {
            return "2019-12-12";
        }
    }



    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;
            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.ContentType = contentType;
                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)
    {
        //Do NOT REMOVE THE \n. It is a request header placeholder
        /*
            GET\n //HTTP Verb
            \n    //Content-Encoding
            \n    //Content-Language
            \n    //Content-Length (empty string when zero)
            \n    //Content-MD5
            \n    //Content-Type
            \n    //Date
            \n    //If-Modified-Since 
            \n    //If-Match
            \n    //If-None-Match
            \n    //If-Unmodified-Since
            \n    //Range
            x-ms-date:Fri, 26 Jun 2015 23:39:12 GMT
            x-ms-version:2015-02-21 //CanonicalizedHeaders
            /myaccount/mycontainer\ncomp:metadata\nrestype:container\ntimeout: 20    //CanonicalizedResource
        */

        string strToSign = $"{method}\n\n\n{contentLength}\n\n{contentType}\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;
    }



   //other code

找到了我的问题的解决方案。 当我通过 POSTMAN 运行请求时,发现 header 中的资源 uri 是 URLEncoded,我没有在我的代码中这样做,我试图上传的文件的名称作为“测试文档”之间的空格.pdf"

来自 PostMan 的回复

'PUT


518262

application/pdf






x-ms-blob-type:BlockBlob
x-ms-date:Mon, 31 Aug 2020 04:41:25 GMT
x-ms-version:2019-12-12
/mystorage/documentcontainer/cricket/test%20document.pdf'

而从我的代码中签名生成的 header 是

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

当我在我的代码中进行以下更改时,通过 URL 编码文件名

string blobName = Uri.EscapeUriString(fileInfo.Name);

它工作正常,文件上传成功。

谢谢大家的帮助。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM