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

PHP使用SAS Token上传图片至Azure Blob时触发认证格式错误

Azure Blob存储SAS Token上传图片认证错误排查

问题描述

使用PHP系统访问Azure Blob存储,公开模式下可正常上传图片,但使用私有SAS Token时收到如下错误:

InvalidAuthenticationInfoAuthentication information is not given in the correct format. Check the value of Authorization header. RequestId: [xxx]

上传相关PHP代码

function getAccessKey(){
    return strval("xxx");
}

function getSASForBlob($accountName,$container, $blob, $resourceType, $permissions, $expiry) {
    $key = getAccessKey();
    
    /* Create the signature */
    $_arraysign = array();
    $_arraysign[] = $permissions;
    $_arraysign[] = '';
    $_arraysign[] = $expiry;
    $_arraysign[] = '/' . $accountName . '/' . $container . '/' . $blob;
    $_arraysign[] = '';
    $_arraysign[] = "2014-02-14"; //the API version is now required
    $_arraysign[] = '';
    $_arraysign[] = '';
    $_arraysign[] = '';
    $_arraysign[] = '';
    $_arraysign[] = '';

    $_str2sign = implode("\n", $_arraysign);

    return base64_encode(
        hash_hmac('sha256', urldecode(utf8_encode($_str2sign)), base64_decode($key), true)
    );
}

function uploadBlob($filepath, $storageAccountname, $containerName, $blobName, $_signature) {

    $accesskey = getAccessKey();
    $extUsed = explode(".",$filepath)[3];
    //$filepath = $filepath.'.'.$extUsed;
    $Date = gmdate('D, d M Y H:i:s \G\M\T');
    $handle = fopen($filepath, "r");
    $fileLen = filesize($filepath);
    $blobName = $blobName."." .$extUsed;
    $URL = "https://$storageAccountname.blob.core.windows.net/$containerName/$blobName";
    
    $expiry = '';
    $resourceType = 'b';
    $permissions = 'r';
    
    $_parts = array();
    $_parts[] = (!empty($expiry))?'se=' . urlencode($expiry):'';
    $_parts[] = 'sr=' . $resourceType;
    $_parts[] = (!empty($permissions))?'sp=' . $permissions:'';
    $_parts[] = 'sig=' . urlencode($_signature);
    $_parts[] = 'sv=2014-02-14';
    
    $URL = $URL . "?" . implode('&', $_parts);

     $headerResource = "x-ms-blob-cache-control:max-age=3600\nx-ms-blob-type:BlockBlob\nx-ms-date:$Date\nx-ms-version:2017-11-09";
    $urlResource = "/$storageAccountname/$containerName/$blobName";
    $extUsed_folded = 'image/'.$extUsed;

    $arraysign = array();
    $arraysign[] = 'PUT';               /*HTTP Verb*/  
    $arraysign[] = '';                  /*Content-Encoding*/  
    $arraysign[] = '';                  /*Content-Language*/  
    $arraysign[] = $fileLen;            /*Content-Length (include value when zero)*/  
    $arraysign[] = '';                  /*Content-MD5*/  
    $arraysign[] = $extUsed_folded          /*'application/pdf'*/;   /*Content-Type*/  
    $arraysign[] = '';                  /*Date*/  
    $arraysign[] = '';                  /*If-Modified-Since */  
    $arraysign[] = '';                  /*If-Match*/  
    $arraysign[] = '';                  /*If-None-Match*/  
    $arraysign[] = '';                  /*If-Unmodified-Since*/  
    $arraysign[] = '';                  /*Range*/  
    $arraysign[] = $headerResource;     /*CanonicalizedHeaders*/
    $arraysign[] = $urlResource;        /*CanonicalizedResource*/

    $str2sign = implode("\n", $arraysign);
    
      $sig = base64_encode(hash_hmac('sha256', urldecode(utf8_encode($str2sign)), base64_decode($accesskey), true));  
  //  $sig = signWithAccountKey($str2sign, getAccessKey());
    $authHeader = "SharedKey $storageAccountname:$sig";

    $header_contentType = "image/" .$extUsed;
    switch($extUsed){
        case "jpg": case "jpeg": default: break;
        case "png":
            $header_contentType = "image/png"; break;
        case "tiff":
            $header_contentType = "image/tiff"; break;
    }
    
    $headers = [
        'Authorization: ' . $authHeader,
        'x-ms-blob-cache-control: max-age=3600',
        'x-ms-blob-type: BlockBlob',
        'x-ms-date: ' . $Date,
        'x-ms-version: 2017-11-09',
        'Content-Type: '.$header_contentType,
        'Content-Length: ' . $fileLen
    ];

    $ch = curl_init();
    curl_setopt ($ch, CURLOPT_URL, $URL );
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
    curl_setopt($ch, CURLOPT_INFILE, $handle); 
    curl_setopt($ch, CURLOPT_INFILESIZE, $fileLen); 
    curl_setopt($ch, CURLOPT_UPLOAD, true); 
    $result = curl_exec($ch);



   // echo ('Uploaded successfully<br/>');
   // print_r($result);

    curl_close($ch);
    
    $resultsList = array( $URL , $filepath , $result , $blobName, $urlResource , $accesskey );
    
    if (isset($resultsList) && is_array($resultsList)) {
        return $resultsList;
    } else {
        return array();
    }
  
}

主调用逻辑

$blobName = 'image-viajes';
$urlAdjunto = '';
$newFilename = '../Imagenes/temp_viajes.jpg';

if (!empty($filename)){
    
    $blobSignature = getSASForBlob('xxx', 'yyy', $blobName,
    'b', 'r', '');
    
    echo '<br /><b>BLOB SIGNATURE</b><p>'.$blobSignature.'</p>';
    
    //$blobFotoAdjunta__url = '';
    $blobFotoAdjunta = uploadBlob(
        $newFilename,
        'xxx', 'yyy',
        $blobName, $blobSignature);

}

核心问题分析

  1. 认证方式冲突:使用SAS Token的同时,请求头中还添加了SharedKey认证头,Azure不允许同时使用两种认证方式,这是触发错误的直接原因。
  2. SAS权限不足:生成SAS时指定的权限是r(只读),但上传操作需要w(写入)权限,权限不匹配会导致操作失败。
  3. API版本不一致:生成SAS用的是2014-02-14版本,请求头里用的是2017-11-09,版本不一致会引发签名验证问题。
  4. Blob名称不匹配:getSASForBlob使用原始$blobName生成签名,但uploadBlob中给Blob添加了后缀,导致签名的资源路径和实际请求路径不一致。

修复步骤

1. 移除SharedKey认证头

在uploadBlob函数中,删除生成$authHeader的代码,同时从$headers数组中移除Authorization相关项,使用SAS Token时无需再用SharedKey认证。

2. 修改SAS权限为写入权限

  • 调用getSASForBlob时,将权限参数从'r'改为'w'(需要读取权限可改为'rw')
  • uploadBlob函数内的$permissions = 'r';同步改为'w'

3. 统一API版本

将生成SAS和请求头的API版本统一为较新版本(如2021-06-08):

  • getSASForBlob中的版本字符串改为"2021-06-08"
  • uploadBlob中的x-ms-version头改为'2021-06-08',SAS参数里的sv=2014-02-14改为sv=2021-06-08

4. 修正Blob名称匹配问题

在调用getSASForBlob前,先构造好带后缀的完整Blob名称,确保签名路径和实际请求路径一致:

// 主调用逻辑中构造完整Blob名称
$extUsed = explode(".", $newFilename)[3];
$fullBlobName = $blobName . "." . $extUsed;
$blobSignature = getSASForBlob('xxx', 'yyy', $fullBlobName, 'b', 'w', $expiry);

5. 补充SAS过期时间

设置合理的过期时间(如1小时),避免空值引发的潜在问题:

$expiry = gmdate('Y-m-d\TH:i:s\Z', time() + 3600);

修复后关键代码片段

修改后的getSASForBlob函数

function getSASForBlob($accountName,$container, $blob, $resourceType, $permissions, $expiry) {
    $key = getAccessKey();
    
    $_arraysign = array();
    $_arraysign[] = $permissions;
    $_arraysign[] = '';
    $_arraysign[] = $expiry;
    $_arraysign[] = '/' . $accountName . '/' . $container . '/' . $blob;
    $_arraysign[] = '';
    $_arraysign[] = "2021-06-08";
    $_arraysign[] = '';
    $_arraysign[] = '';
    $_arraysign[] = '';
    $_arraysign[] = '';
    $_arraysign[] = '';

    $_str2sign = implode("\n", $_arraysign);

    return base64_encode(
        hash_hmac('sha256', urldecode(utf8_encode($_str2sign)), base64_decode($key), true)
    );
}

修改后的主调用逻辑

$blobName = 'image-viajes';
$urlAdjunto = '';
$newFilename = '../Imagenes/temp_viajes.jpg';

if (!empty($filename)){
    $extUsed = explode(".", $newFilename)[3];
    $fullBlobName = $blobName . "." . $extUsed;
    $expiry = gmdate('Y-m-d\TH:i:s\Z', time() + 3600);
    $blobSignature = getSASForBlob('xxx', 'yyy', $fullBlobName, 'b', 'w', $expiry);
    
    echo '<br /><b>BLOB SIGNATURE</b><p>'.$blobSignature.'</p>';
    
    $blobFotoAdjunta = uploadBlob(
        $newFilename,
        'xxx', 'yyy',
        $blobName, $blobSignature, $expiry);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 00:07:33