简体   繁体   English

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

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

I am trying to upload a PDF to a Azure Blob Storage Container using Share Access Key (SAS).我正在尝试使用共享访问密钥(SAS) 将 PDF 上传到 Azure Blob 存储容器。 Code was working fine, but suddenly it started throwing the 403 - Forbidden Exception .代码运行良好,但突然开始抛出403 - Forbidden Exception

Response Status Message:响应状态消息:

Server failed to authenticate the request.服务器无法验证请求。 Make sure the value of Authorization header is formed correctly including the signature.确保正确形成授权 header 的值,包括签名。

As mentioned in this link , one of the possible causes for 403 could be expired a shared access key, hence I refreshed the key and tried with the new one, still no luck.如此链接中所述,403 的可能原因之一可能是共享访问密钥已过期,因此我刷新了密钥并尝试使用新密钥,但仍然没有成功。

I am invoking the UploadBlobToStorageContainer method for a Console Application (.NET Framework 4.6.2)我正在为控制台应用程序 (.NET Framework 4.6.2) 调用UploadBlobToStorageContainer方法

CODE:代码:

    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;
    }

Headers标头

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

Any help would be really appreciated.任何帮助将非常感激。

Thanks Raghunathan S感谢 Raghunathan S

Http 403 is usually caused by authentication failure, please ensure the SAS has enough permission to create and write blob in the specified container, and also ensure that the container itself already exists(as it needs additional permission to create container), for more details about SAS permissions please refer to Create a service SAS and Create an account SAS. Http 403一般是认证失败,请确保SAS有足够的权限在指定容器中创建和写入blob,同时确保容器本身已经存在(因为创建容器需要额外的权限),更多细节SAS权限请参考创建服务SAS创建账号SAS。

An easy way to validate if SAS has enough permissions is to call Azure storage client library's blockBlob.UploadFromFile() in your code, and see if it works or same error is reported there as well.验证 SAS 是否具有足够权限的一种简单方法是在您的代码中调用 Azure 存储客户端库的blockBlob.UploadFromFile() ,并查看它是否有效或是否也报告了相同的错误。

To use container SAS in DMLib:要在 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();

There are some mistakes in your code.您的代码中有一些错误。

1.in the GetAuthorizationHeader() method, please use the following line of code instead of yours: 1.在GetAuthorizationHeader()方法中,请使用以下代码行代替您的代码:

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

2.in the UploadBlobToStorageContainer() method, please add contentType to the request, like below: 2.在UploadBlobToStorageContainer()方法中,请将 contentType 添加到请求中,如下所示:

                request.Method = method;

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

3.For X_MS_VERSION, I suggest that you can use the latest version 2019-12-12, like below: 3.对于X_MS_VERSION,建议使用最新的2019-12-12版本,如下:

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

At last, I posted the working code sample.最后,我发布了工作代码示例。 If you still have problems, please try the code below(Note that I assume the targetFolderPath is empty in the sample, if you want to upload file to a directory, please adjust the code according):如果仍然有问题,请尝试下面的代码(注意,我假设示例中的 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

Found the solution for my problem.找到了我的问题的解决方案。 When i ran the request through POSTMAN, figured out that then resource uri in the header is URLEncoded, which i didn't do in my code and the name of the file that i am trying to upload as a space in-between "test document.pdf"当我通过 POSTMAN 运行请求时,发现 header 中的资源 uri 是 URLEncoded,我没有在我的代码中这样做,我试图上传的文件的名称作为“测试文档”之间的空格.pdf"

Response from PostMan来自 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'

whereas the header generated for signing from my code is而从我的代码中签名生成的 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

When i made the following change in my code, by URL encoding the file name当我在我的代码中进行以下更改时,通过 URL 编码文件名

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

it worked and the files got uploaded successfully.它工作正常,文件上传成功。

Thanks Guys for the Help.谢谢大家的帮助。

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

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