ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

http+TLS实现加密(二)---openssl生成证书及利用mongoose实现简单RESTAPI_HTTPS_SERVER服务器测试

http+TLS实现加密(二)---openssl生成证书及利用mongoose实现简单RESTAPI_HTTPS_SERVER服务器测试 1. 为什么本地 RESTAPI 要跑 HTTPS从明文到 TLS 加密链路如果你正在用 mongoose 写一个本地 RESTAPI 服务默认跑的是 HTTP请求和响应在网络里是明文的。局域网里随便一个抓包工具就能看到你传的参数、返回的 JSON甚至登录 token。要解决这个问题最直接的办法就是给服务套一层 TLS也就是我们常说的 HTTPS。HTTPS 的本质是 HTTP 跑在 TLS 之上TLS 负责握手、协商密钥、加密数据HTTP 只管业务。要让 mongoose 支持 HTTPS你需要两样东西一份服务器私钥key和一份服务器证书crt。证书可以自签也可以由 CA 签发。本地测试环境用自签证书就够了成本为零链路一样是加密的。这篇是「httpTLS 实现加密」系列的第二篇聚焦一件事用 openssl 生成自签证书然后让 mongoose 搭建的 RESTAPI HTTPS 服务器真正跑起来最后用 curl 验证加密链路是否生效。适合正在做本地 API 调试、嵌入式 HTTP 服务、或者想搞懂 TLS 握手到底怎么落地的同学。下面所有命令和代码都可以直接复制复现。2. 前置准备openssl 与 mongoose 环境确认在动手生成证书之前先把工具链确认一遍避免后面报一些莫名其妙的错。openssl 基本是 Linux/macOS 自带的Windows 下可以用 Git Bash 里带的版本或者装一个完整的 openssl。确认命令openssl version正常会输出类似OpenSSL 3.0.2 15 Mar 2022。如果提示找不到命令先装 openssl 再继续。mongoose 这边你需要拿到mongoose.c和mongoose.h两个文件这是它的 amalgamated 版本单文件集成非常方便。编译时链接 ssl 和 crypto 两个库即可。确认你的系统有这两个库ls /usr/lib/x86_64-linux-gnu/libssl.so /usr/lib/x86_64-linux-gnu/libcrypto.so如果路径不对用ldconfig -p | grep libssl找一下实际位置。另外 mongoose 编译时需要开启 SSL 宏也就是MG_ENABLE_SSL1这个后面编译脚本里会体现。注意mongoose 不同大版本的 API 差异比较大6.x 和 7.x 的绑定函数、事件回调写法不一样。本文以 6.14 这一代的 API 为基准如果你用的是 7.x绑定部分需要按官方示例调整但证书生成和 curl 验证逻辑完全一致。3. 用 openssl 生成自签服务器证书证书生成分三步生成私钥、生成证书请求 CSR、自签生成证书。这里走的是「无 CA 的单向认证」路线也就是只有服务器证书客户端不校验 CA适合本地测试。3.1 生成服务器私钥 server.keyopenssl genrsa -des3 -out server.key 2048执行后会提示输入 pass phrase这是给私钥加的密码。-des3表示用 3DES 加密私钥2048是密钥长度。这里建议用 2048 而不是 10241024 位在现代安全标准下已经偏弱。如果你不想每次启动服务都输密码可以生成不带密码的私钥openssl genrsa -out server.key 2048本地测试图方便可以用这种但生产环境一定要带密码或者用更严格的密钥管理。3.2 生成证书签名请求 server.csropenssl req -new -key server.key -out server.csr这一步会交互式问你一堆信息最关键的是Common Name这里必须填你服务器实际使用的 IP 或者域名。比如你的服务跑在172.17.190.85就填这个 IP如果是本地就是127.0.0.1或localhost。填错了后面 curl 校验会报 common name 不匹配。其他字段随便填国家、省份、组织这些不影响本地测试。示例交互Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:shanghai Locality Name (eg, city) []:shanghai Organization Name (eg, company) [Internet Widgits Pty Ltd]:test Organizational Unit Name (eg, section) []:dev Common Name (e.g. server FQDN or YOUR name) []:172.17.190.85 Email Address []:3.3 自签生成服务器证书 server.crtopenssl req -x509 -days 3650 -key server.key -in server.csr -out server.crt-x509表示直接生成自签证书-days 3650是有效期十年。执行完你会得到三个文件server.key、server.csr、server.crt。csr 在自签完成后其实可以删掉但留着方便以后重新签。验证一下证书内容openssl x509 -noout -text -in server.crt你会看到 Issuer 和 Subject 是同一个因为是自己签自己。再验证一下openssl verify -CAfile server.crt server.crt输出server.crt: OK就说明证书本身没问题。4. mongoose HTTPS 服务端配置骨架证书有了接下来让 mongoose 用上它。核心改动就一处在mg_bind_opt之前把bind_opts.ssl_cert和bind_opts.ssl_key指向你的证书和私钥文件。4.1 服务端代码骨架下面是一个精简版的 RESTAPI HTTPS 服务器保留了/api/v1/sum这个加法接口方便验证#include mongoose.h static const char *s_http_port 9001; static struct mg_serve_http_opts s_http_server_opts; static void handle_sum_call(struct mg_connection *nc, struct http_message *hm) { char n1[100], n2[100]; double result; mg_get_http_var(hm-body, n1, n1, sizeof(n1)); mg_get_http_var(hm-body, n2, n2, sizeof(n2)); mg_printf(nc, %s, HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n); result strtod(n1, NULL) strtod(n2, NULL); mg_printf_http_chunk(nc, { \result\: %lf }, result); mg_send_http_chunk(nc, , 0); } static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { struct http_message *hm (struct http_message *) ev_data; switch (ev) { case MG_EV_HTTP_REQUEST: if (mg_vcmp(hm-uri, /api/v1/sum) 0) { handle_sum_call(nc, hm); } else { mg_serve_http(nc, hm, s_http_server_opts); } break; default: break; } } int main(int argc, char *argv[]) { struct mg_mgr mgr; struct mg_connection *nc; struct mg_bind_opts bind_opts; const char *err_str; const char *ssl_cert server.crt; const char *ssl_key server.key; mg_mgr_init(mgr, NULL); s_http_server_opts.document_root .; memset(bind_opts, 0, sizeof(bind_opts)); bind_opts.error_string err_str; bind_opts.ssl_cert ssl_cert; bind_opts.ssl_key ssl_key; nc mg_bind_opt(mgr, s_http_port, ev_handler, bind_opts); if (nc NULL) { fprintf(stderr, Error starting server on port %s: %s\n, s_http_port, *bind_opts.error_string); return 1; } mg_set_protocol_http_websocket(nc); printf(Starting HTTPS RESTful server on port %s\n, s_http_port); for (;;) { mg_mgr_poll(mgr, 1000); } mg_mgr_free(mgr); return 0; }关键点bind_opts.ssl_cert和bind_opts.ssl_key一旦设置mongoose 就会用 TLS 监听这个端口所有进来的连接都走加密握手。4.2 编译脚本用 CMake 编译注意链接 ssl 和 cryptocmake_minimum_required(VERSION 3.5) project(https_server) include_directories(${PROJECT_SOURCE_DIR}) add_executable(https_server https_server.c mongoose.c) target_link_libraries(https_server ssl crypto)编译mkdir build cd build cmake .. make编译产物https_server会出现在 build 目录下。把server.crt和server.key放到可执行文件同级目录或者用绝对路径指定。5. 启动服务与 curl 验证加密链路服务编译好了证书也齐了现在跑起来验证。5.1 启动 HTTPS 服务./https_server如果私钥带密码会提示Enter PEM pass phrase:输入你生成时设的密码。看到Starting HTTPS RESTful server on port 9001就说明服务起来了。5.2 用 curl 验证先直接请求不加任何跳过校验的参数curl -v https://172.17.190.85:9001/api/v1/sum -d n11n22你会看到类似报错SSL certificate problem: self signed certificate curl: (60) SSL certificate problem: self signed certificate这是正常的因为自签证书不在系统信任列表里。curl 默认会校验证书链自签证书没有 CA 背书所以拒绝。本地测试用-k跳过校验curl -vk https://172.17.190.85:9001/api/v1/sum -d n11n22输出里重点看这几行* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 * Server certificate: * subject: CCN; STshanghai; Lshanghai; Otest; OUdev; CN172.17.190.85 * start date: ... * expire date: ... * common name: 172.17.190.85 (matched) HTTP/1.1 200 OK Transfer-Encoding: chunked { result: 3.000000 }看到SSL connection using TLSv1.3说明 TLS 握手成功common name matched说明证书里的 CN 和你请求的地址一致最后返回{ result: 3.000000 }说明业务逻辑也通了。整条加密链路从握手到业务响应全部跑通。5.3 用 openssl s_client 看握手细节想更深入看握手过程可以用openssl s_client -connect 172.17.190.85:9001 -servername 172.17.190.85它会打印出完整的证书链、协商的加密套件、TLS 版本。输入GET /api/v1/sum之类的 HTTP 请求也能手动测。这个工具在排查 TLS 握手失败时特别有用。6. 本篇常见错误排查跑这个流程时最容易踩的坑集中在证书和编译两块下面按报错现象逐个说。6.1 curl 报 common name 不匹配报错类似SSL: certificate subject name xxx does not match target host name yyy原因是你生成 CSR 时 Common Name 填的地址和 curl 请求的地址不一致。比如 CN 填了localhost但 curl 请求的是172.17.190.85。解决办法是重新生成证书CN 填实际访问的 IP 或域名。如果同时要用多个地址访问可以在生成时加 SAN 扩展openssl req -x509 -days 3650 -key server.key -in server.csr -out server.crt \ -addext subjectAltNameIP:172.17.190.85,IP:127.0.0.1,DNS:localhost6.2 服务启动报 ssl_cert 加载失败如果 mongoose 启动时报证书加载失败先确认server.crt和server.key的路径对不对。相对路径是相对于可执行文件的工作目录不是源码目录。用绝对路径最稳。另外确认私钥密码输入正确密码错了也会加载失败。6.3 编译报 undefined reference to SSL_xxx这是没链接 ssl 库。检查 CMake 里target_link_libraries有没有写ssl crypto。如果系统库路径特殊可能需要加link_directories。用pkg-config --libs openssl可以拿到正确的链接参数。6.4 浏览器访问被拦截浏览器对自签证书的拦截比 curl 严格会直接显示「您的连接不是私密连接」。本地测试点「高级」→「继续前往」即可。如果想让浏览器也信任需要把server.crt导入到系统的受信任根证书颁发机构里。这一步只建议在测试机上做不要在生产环境导入自签证书。6.5 端口被占用报错Error starting server on port 9001先确认端口没被别的进程占用lsof -i :9001有占用就换端口或者杀掉占用进程。7. 把证书链路接到实际 API 调试流程里本地 HTTPS 跑通之后下一步通常是把这套服务接到真实的 API 调试或者模型调用流程里。如果你在本地用 mongoose 搭的是一个代理层或者网关层后面要对接大模型 API那证书这块的配置逻辑是一样的服务端持有 key 和 crt客户端要么信任你的自签 CA要么在测试阶段跳过校验。实际做模型对话调试、coding plan 验证或者 Agent 工具链联调时我一般会把本地 HTTPS 服务和远端 API 分开管理。本地服务用自签证书保证链路加密远端调用则走标准的 HTTPS 客户端配置。如果你需要快速验证模型接口的返回结构可以直接在模型对话页面里试请求省去本地搭服务的步骤如果是长期跑编码任务或者 Agent 流程用 Coding Plan 这类按周期计费的方式会比按次调用更划算接入细节和密钥管理则看接入文档和 API Keys 页面把 key 的生成、轮换、权限控制理清楚再往本地服务里塞。证书这块的经验是自签证书的 CN 和 SAN 一定要覆盖你所有实际访问的地址否则每次换 IP 调试都要重新签很烦。另外私钥别提交到 git加个.gitignore把*.key排除掉。本地测试用-k跳过校验没问题但任何要上线的服务都必须走正规 CA 签发的证书自签只适合开发和内网测试。
返回列表