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
相关产品推荐
相关产品推荐

