C++开发SAML SP:如何压缩/解压URL中的XML?
解决C++中SAML请求XML的压缩与编码问题
嘿,我来帮你搞定SAML请求压缩这个难题!SAML规范里明确要求,通过GET方式传递的SAMLRequest参数需要经过DEFLATE压缩 + Base64编码 + URL编码这三步(其中URL编码是为了安全传输,避免特殊字符破坏URL结构)。你已经掌握了Base64,那核心的压缩部分我来给你详细讲清楚。
关键知识点
SAML标准规定的压缩方式是原始DEFLATE算法(注意不是gzip,gzip会额外添加文件头和尾,不符合SAML要求)。我们可以用C++常用的zlib库来实现这个压缩。
完整实现步骤及代码
1. 依赖准备
首先确保你的项目链接了zlib库,编译时需要加上-lz参数(比如GCC编译命令:g++ your_code.cpp -o your_app -lz)。
2. DEFLATE压缩函数
这个函数会把UTF-8编码的XML字符串压缩成原始DEFLATE二进制数据:
#include <zlib.h> #include <string> #include <vector> #include <stdexcept> #include <cstring> #include <cstdio> #include <cctype> std::vector<unsigned char> deflate_compress(const std::string& input) { z_stream zs; std::memset(&zs, 0, sizeof(zs)); // 初始化压缩器,-MAX_WBITS表示使用原始DEFLATE(无gzip头) if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK) { throw std::runtime_error("Failed to initialize deflate compressor"); } zs.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(input.data())); zs.avail_in = input.size(); std::vector<unsigned char> output_buffer; char temp_buffer[32768]; // 32KB缓冲区,平衡性能和内存占用 do { zs.next_out = reinterpret_cast<Bytef*>(temp_buffer); zs.avail_out = sizeof(temp_buffer); int ret = deflate(&zs, Z_FINISH); if (ret != Z_OK && ret != Z_STREAM_END) { deflateEnd(&zs); throw std::runtime_error("Deflate compression failed with code: " + std::to_string(ret)); } size_t bytes_written = sizeof(temp_buffer) - zs.avail_out; output_buffer.insert(output_buffer.end(), temp_buffer, temp_buffer + bytes_written); } while (zs.avail_out == 0); deflateEnd(&zs); return output_buffer; }
3. Base64编码函数
这里提供一个标准Base64实现(你如果有现成的可靠实现也可以直接用):
std::string base64_encode(const std::vector<unsigned char>& data) { const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; std::string result; int i = 0; unsigned char char_array_3[3]; unsigned char char_array_4[4]; for (unsigned char c : data) { char_array_3[i++] = c; if (i == 3) { char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for (i = 0; i < 4; i++) { result += base64_chars[char_array_4[i]]; } i = 0; } } // 处理剩余不足3字节的情况 if (i > 0) { for (int j = i; j < 3; j++) { char_array_3[j] = '\0'; } char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for (int j = 0; j < i + 1; j++) { result += base64_chars[char_array_4[j]]; } while (i++ < 3) { result += '='; } } return result; }
4. URL编码函数
Base64中的+、/、=都是URL特殊字符,必须转义才能安全传递:
std::string url_encode(const std::string& input) { std::string result; for (char c : input) { if (c == '+') { result += "%2B"; } else if (c == '/') { result += "%2F"; } else if (c == '=') { result += "%3D"; } else if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { // 这些字符是URL安全的,直接保留 result += c; } else { // 其他特殊字符按URL编码规则转义 char buf[10]; std::snprintf(buf, sizeof(buf), "%%%02X", static_cast<unsigned char>(c)); result += buf; } } return result; }
5. 整合使用示例
把上面的函数整合起来,生成符合要求的SAML请求URL:
int main() { // 替换成你的实际SAML请求XML std::string saml_xml = R"(<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <samlp:AuthnRequest xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:enc="http://www.w3.org/2001/04/xmlenc#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="your-unique-id" Version="2.0" IssueInstant="2024-05-20T12:00:00Z" Destination="http://acme.com:16006/idp/samlv20" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="http://your-sp-domain.com/acs"> <saml:Issuer>http://your-sp-domain.com</saml:Issuer> <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" AllowCreate="true"/> </samlp:AuthnRequest>)"; try { // 步骤1:DEFLATE压缩XML auto compressed_data = deflate_compress(saml_xml); // 步骤2:Base64编码压缩后的数据 std::string base64_result = base64_encode(compressed_data); // 步骤3:URL编码Base64结果 std::string url_safe_result = url_encode(base64_result); // 构造最终的请求URL std::string final_url = "http://acme.com:16006/idp/samlv20?SAMLRequest=" + url_safe_result; std::cout << "最终请求URL:\n" << final_url << std::endl; } catch (const std::exception& e) { std::cerr << "处理出错:" << e.what() << std::endl; return 1; } return 0; }
注意事项
- 确保你的XML是UTF-8编码,SAML标准强制要求这一点。
- 压缩时必须使用原始DEFLATE模式(通过
-MAX_WBITS参数设置),不能用gzip,否则IDP会无法解析。 - 如果你用的是第三方Base64库,要确认是标准Base64,不是URL安全的Base64(之后再单独做URL编码更稳妥)。
内容的提问来源于stack exchange,提问作者gaurav
相关产品推荐
相关产品推荐

