Azure BLOB 存储 REST API 上传文件引发 403 - 禁止访问
Azure BLOB Storage REST API Uploading a file throws 403 - Forbidden
我正在尝试使用 共享访问密钥 (SAS) 将 PDF 上传到 Azure Blob 存储容器。
代码运行良好,但突然开始抛出 403 - Forbidden Exception.
响应状态消息:
服务器未能验证请求。确保授权值 header 的格式正确,包括签名。
如本 link 所述,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;
}
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
任何帮助将不胜感激。
谢谢
拉古纳坦 S
HTTP 403一般是认证失败造成的,请确保SAS有足够的权限在指定的容器中创建和写入blob,同时确保容器本身已经存在(因为创建容器需要额外的权限) , 有关 SAS 权限的更多详细信息,请参阅 Create a service SAS and Create an account 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.in GetAuthorizationHeader()
方法,请使用下面这行代码代替你的:
string strToSign = $"{method}\n\n\n{contentLength}\n\n{contentType}\n\n\n\n\n\n\n{xmsHeader}\n{resHeader}";
2.inUploadBlobToStorageContainer()
方法,请在请求中添加contentType,如下:
request.Method = method;
//add contentType here
request.ContentType = contentType;
request.ContentLength = contentLength;
request.Headers.Add("x-ms-blob-type", blobType);
3.ForX_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 是 URL 编码的,我在我的代码和文件名中没有这样做我正在尝试上传为 space in-between“测试 document.pdf”
邮递员的回复
'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);
成功,文件上传成功。
感谢大家的帮助。
我正在尝试使用 共享访问密钥 (SAS) 将 PDF 上传到 Azure Blob 存储容器。 代码运行良好,但突然开始抛出 403 - Forbidden Exception.
响应状态消息:
服务器未能验证请求。确保授权值 header 的格式正确,包括签名。
如本 link 所述,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;
}
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
任何帮助将不胜感激。
谢谢 拉古纳坦 S
HTTP 403一般是认证失败造成的,请确保SAS有足够的权限在指定的容器中创建和写入blob,同时确保容器本身已经存在(因为创建容器需要额外的权限) , 有关 SAS 权限的更多详细信息,请参阅 Create a service SAS and Create an account 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.in GetAuthorizationHeader()
方法,请使用下面这行代码代替你的:
string strToSign = $"{method}\n\n\n{contentLength}\n\n{contentType}\n\n\n\n\n\n\n{xmsHeader}\n{resHeader}";
2.inUploadBlobToStorageContainer()
方法,请在请求中添加contentType,如下:
request.Method = method;
//add contentType here
request.ContentType = contentType;
request.ContentLength = contentLength;
request.Headers.Add("x-ms-blob-type", blobType);
3.ForX_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 是 URL 编码的,我在我的代码和文件名中没有这样做我正在尝试上传为 space in-between“测试 document.pdf”
邮递员的回复
'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);
成功,文件上传成功。
感谢大家的帮助。