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

C++生成Azure Storage SAS Token失败:签名格式错误排查

Azure Storage SAS Token生成报错「Signature fields not well formed」

尝试多种方法生成Azure Storage SAS Token时,始终返回如下认证错误:

<?xml version="1.0" encoding="utf-8"?><Error><Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:32b24722-801e-0024-1e9c-3b3c1d000000 Time:2024-11-20T22:36:21.0299082Z</Message><AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail></Error>

通过Azure门户生成的SAS Token可正常使用,确认存储账户、容器及访问密钥有效(已做Base64解码用于签名)。以下是下载文件及Token生成代码,怀疑签名算法存在基础错误,查阅官方文档后未解决问题。

下载文件代码

bool downloadFile(const std::string& blobName, const std::string& localFilePath) {
        if (!m_curl) {
            std::cerr << "Failed to initialize cURL" << std::endl;
            return false;
        }

        // Generate SAS token just before the file download
        //IotHubHelpers iotHubHelper;
        std::string resourceUri = m_storage_account + ".blob.core.windows.net/" + m_container_name + "/" + blobName;
        std::string account_key = "Only Half the eyJBuYU2S2G99MDFfb5K6aGRrZJRdAlonKchD+AStQfq0Ig==";  // Your container access key
        std::string start_time = "2024-11-19T19:21:22Z";
        std::string expiry_time = "2028-11-20T03:21:22Z";
        std::string permissions = "rwdlacupiyx";


         std::string sasToken = generate_blob_sas_token(
            //m_storage_account,
            //m_container_name,
            //blobName,
            account_key //,
            //permissions,
            //start_time,
            //expiry_time
        );

        if (sasToken.empty()) {
            std::cerr << "Error: Failed to generate SAS token." << std::endl;
            return false;
        }

        std::cout << "SAS Token: " << sasToken << std::endl;

        // Construct URL with SAS token
        std::string url = "https://" + resourceUri + "?" + sasToken;
        std::cout << "Downloading from URL: " << url << std::endl;

        // Open the file stream in binary mode
        std::ofstream outFile(localFilePath, std::ios::binary);
        if (!outFile) {
            std::cerr << "Failed to open file for writing: " << localFilePath << std::endl;
            return false;
        }

        // Set cURL options
        curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, writeData);
        curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &outFile);
        curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L);
        curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 2L);

        CURLcode res = curl_easy_perform(m_curl);
        if (res != CURLE_OK) {
            std::cerr << "cURL error: " << curl_easy_strerror(res) << std::endl;
            outFile.close();
            return false;
        }

        outFile.close();
        return true;
    }

当前Token生成代码(无法正常工作)

std::string generate_blob_sas_token(const std::string& key) {
        const std::string canonicalizedResource = "/blob/fotacontainer/fota/testfile.txt";

        std::vector<std::pair<std::string, std::string>> sas_token_properties = {
            {"sp", "r"},
            {"st", "2024-11-19T19:21:22Z"}, //get_utc_time(-120)},
            {"se", "2025-11-19T19:21:22Z"}, //get_utc_time(1440)},
            {"canonicalizedResource", canonicalizedResource},
            {"si", ""},
            {"sip", ""},
            {"spr", "https"},
            {"sv", "2023-01-03"},
            {"sr", "b"},
            {"sst", ""},
            {"ses", ""},
            {"rscc", ""},
            {"rscd", ""},
            {"rsce", ""},
            {"rscl", ""},
            {"rsct", ""}
        };

        std::vector<std::string> values;
        for (const auto& entry : sas_token_properties) {
            values.push_back(entry.second);
        }

        std::string string_to_sign = join_with_newline(values);
        std::cout << string_to_sign << std::endl;

        // The keys we get from the storage account are base64 encoded, so we need to decode them first
        std::string decoded_key = base64_decode(key);
        unsigned char* digest = HMAC(EVP_sha256(), decoded_key.data(), decoded_key.size(),
                                     reinterpret_cast<const unsigned char*>(string_to_sign.data()), string_to_sign.size(), nullptr, nullptr);

        std::string signature = base64_encode(digest, SHA256_DIGEST_LENGTH);

        std::vector<std::string> parameters;
        for (const auto& entry : sas_token_properties) {
            if (!entry.second.empty() && entry.first != "canonicalizedResource") {
                parameters.push_back(entry.first + "=" + url_encode(entry.second));
            }
        }
        parameters.push_back("sig=" + url_encode(signature));

        return join(parameters, "&");
}

问题根源及修正方案

1. 签名字符串格式错误

对于服务版本2023-01-03的Blob SAS,签名字符串的字段必须严格按顺序排列,且仅包含非空值字段。当前代码将所有字段(包括空值)都加入了签名,导致格式不合法。同时canonicalizedResource格式错误,正确格式应为/[存储账户名]/[容器名]/[Blob名],缺少存储账户名将导致资源识别错误。

2. 函数参数缺失

generate_blob_sas_token未接收存储账户、容器、Blob名等动态参数,硬编码资源路径无法适配不同Blob,也不符合SAS签名的动态生成要求。

修正后的Token生成代码

std::string generate_blob_sas_token(
    const std::string& account_name,
    const std::string& container_name,
    const std::string& blob_name,
    const std::string& account_key,
    const std::string& permissions,
    const std::string& start_time,
    const std::string& expiry_time
) {
    // 构造正确的Canonicalized Resource
    const std::string canonicalizedResource = "/" + account_name + "/" + container_name + "/" + blob_name;

    // 按SAS规范顺序排列字段,仅保留非空值
    std::vector<std::pair<std::string, std::string>> sas_fields = {
        {"sp", permissions},
        {"st", start_time},
        {"se", expiry_time},
        {"spr", "https"},
        {"sv", "2023-01-03"},
        {"sr", "b"},
        {"canonicalizedResource", canonicalizedResource}
    };

    // 生成签名字符串:仅包含非空字段的值,按顺序用换行符分隔
    std::vector<std::string> sign_values;
    for (const auto& entry : sas_fields) {
        if (!entry.second.empty()) {
            sign_values.push_back(entry.second);
        }
    }
    std::string string_to_sign = join_with_newline(sign_values);

    // 解码账户密钥并生成HMAC-SHA256签名
    std::string decoded_key = base64_decode(account_key);
    unsigned char digest[SHA256_DIGEST_LENGTH];
    HMAC(EVP_sha256(), decoded_key.data(), decoded_key.size(),
         reinterpret_cast<const unsigned char*>(string_to_sign.data()), string_to_sign.size(), digest, nullptr);

    std::string signature = base64_encode(digest, SHA256_DIGEST_LENGTH);

    // 生成SAS参数(排除canonicalizedResource)
    std::vector<std::string> parameters;
    for (const auto& entry : sas_fields) {
        if (!entry.second.empty() && entry.first != "canonicalizedResource") {
            parameters.push_back(entry.first + "=" + url_encode(entry.second));
        }
    }
    parameters.push_back("sig=" + url_encode(signature));

    return join(parameters, "&");
}

修正下载函数的调用

在downloadFile中修改Token生成调用:

std::string sasToken = generate_blob_sas_token(
    m_storage_account,
    m_container_name,
    blobName,
    account_key,
    permissions,
    start_time,
    expiry_time
);

额外注意事项

  • 确保url_encode函数正确实现,对特殊字符(如/、=、+等)进行URL编码
  • base64_decode需正确处理账户密钥的Base64编码,避免解码错误
  • 时间格式必须为UTC标准格式(YYYY-MM-DDTHH:MM:SSZ),且确保当前时间在st和se范围内

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:07:34