使用PHP通过REST API上传Azure Blob Storage时认证失败求助
Azure Blob Storage PHP REST API 上传图片 AuthenticationFailed 错误排查与修正
问题描述
使用PHP通过REST API上传图片到Azure Blob Storage时,持续收到AuthenticationFailed错误,服务器提示HTTP请求中的MAC签名与计算出的签名不匹配,并给出了用于签名的字符串。
报错信息
AuthenticationFailedServer failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId: <Request ID> Time:2023-04-13T20:31:58.4450928Z The MAC signature found in the HTTP request '...' is not the same as any computed signature. Server used following string to sign: 'PUT 11746 image/png x-ms-blob-cache-control:max-age=259200 x-ms-blob-type:BlockBlob x-ms-date:Thu, 13 Apr 2023 20:31:58 GMT x-ms-version:2017-04-17 /<storage account>/<container name>/<blob name>'.
原代码
function upload_blob( $file_to_upload, $blob_name // "image.png" ){ $access_key = "<Access key>"; $storage_account = "<Storage account>"; $container_name = "<Container name>"; $destination_url = "https://$storage_account.blob.core.windows.net/$container_name/$blob_name"; $mime_type = mime_content_type($file_to_upload); $file_handle = fopen($file_to_upload, "r"); $current_date = gmdate("D, d M Y H:i:s T", time()); $file_length = filesize($file_to_upload); $header_resource = "x-ms-blob-cache-control:max-age=2592000 x-ms-blob-type:BlockBlob x-ms-date:$current_date x-ms-version:2017-04-17"; $url_resource = "/$storage_account/$container_name/$blob_name"; $str_to_sign = "PUT $file_length $mime_type $header_resource $url_resource"; $signature = base64_encode(hash_hmac("sha256", urldecode(utf8_encode($str_to_sign)), base64_decode($access_key), true)); $auth_header = "SharedKey $storage_account:$signature"; $headers = [ "Authorization: " . $auth_header, "x-ms-blob-cache-control: max-age=259200", "x-ms-blob-type: BlockBlob", "x-ms-date: " . $current_date, "x-ms-version: 2017-04-17", "Content-Type: $mime_type", "Content-Length: " . $file_length ]; $curl_handle = curl_init($destination_url); curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl_handle, CURLOPT_HTTPHEADER, $headers); curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, "PUT"); curl_setopt($curl_handle, CURLOPT_INFILE, $file_handle); curl_setopt($curl_handle, CURLOPT_INFILESIZE, $file_length); curl_setopt($curl_handle, CURLOPT_UPLOAD, true); $result = curl_exec($curl_handle); echo "<pre>"; print_r("Result:"); print_r($result); print_r("============================================================"); echo "</pre>"; curl_close($curl_handle); }
错误点分析
- 缓存控制值不一致:构造签名用的
x-ms-blob-cache-control值为max-age=2592000,但实际请求头和服务器签名字符串中均为max-age=259200,直接导致签名计算偏差。 - 签名字符串空行数量不符:对比服务器给出的签名字符串,原代码在
Content-Type(即$mime_type)之后的空行数量不足,Azure要求所有未使用的标准HTTP头字段必须留空行占位。 - 多余的字符串编码处理:对签名字符串执行
urldecode(utf8_encode(...))是错误操作,Azure要求直接使用原始字符串计算签名,无需额外编码转换。
修正后的代码
function upload_blob( $file_to_upload, $blob_name // "image.png" ){ $access_key = "<Access key>"; $storage_account = "<Storage account>"; $container_name = "<Container name>"; $destination_url = "https://$storage_account.blob.core.windows.net/$container_name/$blob_name"; $mime_type = mime_content_type($file_to_upload); $file_handle = fopen($file_to_upload, "r"); $current_date = gmdate("D, d M Y H:i:s T", time()); $file_length = filesize($file_to_upload); // 修正缓存控制值,与请求头保持一致 $header_resource = "x-ms-blob-cache-control:max-age=259200 x-ms-blob-type:BlockBlob x-ms-date:$current_date x-ms-version:2017-04-17"; $url_resource = "/$storage_account/$container_name/$blob_name"; // 严格按照服务器给出的格式构造签名字符串,确保空行数量匹配 $str_to_sign = "PUT $file_length $mime_type $header_resource $url_resource"; // 移除多余的编码处理,直接使用原始字符串计算签名 $signature = base64_encode(hash_hmac("sha256", $str_to_sign, base64_decode($access_key), true)); $auth_header = "SharedKey $storage_account:$signature"; $headers = [ "Authorization: " . $auth_header, "x-ms-blob-cache-control: max-age=259200", "x-ms-blob-type: BlockBlob", "x-ms-date: " . $current_date, "x-ms-version: 2017-04-17", "Content-Type: $mime_type", "Content-Length: " . $file_length ]; $curl_handle = curl_init($destination_url); curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl_handle, CURLOPT_HTTPHEADER, $headers); curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, "PUT"); curl_setopt($curl_handle, CURLOPT_INFILE, $file_handle); curl_setopt($curl_handle, CURLOPT_INFILESIZE, $file_length); curl_setopt($curl_handle, CURLOPT_UPLOAD, true); $result = curl_exec($curl_handle); echo "<pre>"; print_r("Result:"); print_r($result); print_r("============================================================"); echo "</pre>"; curl_close($curl_handle); }
额外说明
- 构造签名字符串时,必须严格遵循Azure SharedKey签名规则:所有未使用的标准HTTP头字段(如Content-Encoding、Content-MD5等)需留空行占位,顺序不可错乱。
- 确保
x-ms-date使用GMT时间,且签名计算与请求发送的时间一致,避免因时间差导致签名失效。 - 生产环境建议开启
CURLOPT_SSL_VERIFYPEER和CURLOPT_SSL_VERIFYHOST,关闭该选项会带来安全风险。
内容的提问来源于stack exchange,提问作者Pape
相关产品推荐
相关产品推荐

