Node系列 · Node基础:https 模块

Node系列 · Node基础:https 模块

HTTPS = HTTP over TLS。在浏览器地址栏看到的锁图标、API 调用方与网关的加密通道、银行/支付接口的合规要求------全都靠 TLS。本章讲清楚 TLS 握手机制和 Node 中如何启用 HTTPS,包括本地开发必备的自签名证书。

一、HTTPS = HTTP + TLS

HTTPS = HTTP + TLS,本质是在 HTTP 协议下面再套一层加密传输层:
#mermaid-svg-szdd4Qvqkx98zlHL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-szdd4Qvqkx98zlHL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-szdd4Qvqkx98zlHL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-szdd4Qvqkx98zlHL .error-icon{fill:#552222;}#mermaid-svg-szdd4Qvqkx98zlHL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-szdd4Qvqkx98zlHL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-szdd4Qvqkx98zlHL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-szdd4Qvqkx98zlHL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-szdd4Qvqkx98zlHL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-szdd4Qvqkx98zlHL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-szdd4Qvqkx98zlHL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-szdd4Qvqkx98zlHL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-szdd4Qvqkx98zlHL .marker.cross{stroke:#333333;}#mermaid-svg-szdd4Qvqkx98zlHL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-szdd4Qvqkx98zlHL p{margin:0;}#mermaid-svg-szdd4Qvqkx98zlHL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-szdd4Qvqkx98zlHL .cluster-label text{fill:#333;}#mermaid-svg-szdd4Qvqkx98zlHL .cluster-label span{color:#333;}#mermaid-svg-szdd4Qvqkx98zlHL .cluster-label span p{background-color:transparent;}#mermaid-svg-szdd4Qvqkx98zlHL .label text,#mermaid-svg-szdd4Qvqkx98zlHL span{fill:#333;color:#333;}#mermaid-svg-szdd4Qvqkx98zlHL .node rect,#mermaid-svg-szdd4Qvqkx98zlHL .node circle,#mermaid-svg-szdd4Qvqkx98zlHL .node ellipse,#mermaid-svg-szdd4Qvqkx98zlHL .node polygon,#mermaid-svg-szdd4Qvqkx98zlHL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-szdd4Qvqkx98zlHL .rough-node .label text,#mermaid-svg-szdd4Qvqkx98zlHL .node .label text,#mermaid-svg-szdd4Qvqkx98zlHL .image-shape .label,#mermaid-svg-szdd4Qvqkx98zlHL .icon-shape .label{text-anchor:middle;}#mermaid-svg-szdd4Qvqkx98zlHL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-szdd4Qvqkx98zlHL .rough-node .label,#mermaid-svg-szdd4Qvqkx98zlHL .node .label,#mermaid-svg-szdd4Qvqkx98zlHL .image-shape .label,#mermaid-svg-szdd4Qvqkx98zlHL .icon-shape .label{text-align:center;}#mermaid-svg-szdd4Qvqkx98zlHL .node.clickable{cursor:pointer;}#mermaid-svg-szdd4Qvqkx98zlHL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-szdd4Qvqkx98zlHL .arrowheadPath{fill:#333333;}#mermaid-svg-szdd4Qvqkx98zlHL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-szdd4Qvqkx98zlHL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-szdd4Qvqkx98zlHL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-szdd4Qvqkx98zlHL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-szdd4Qvqkx98zlHL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-szdd4Qvqkx98zlHL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-szdd4Qvqkx98zlHL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-szdd4Qvqkx98zlHL .cluster text{fill:#333;}#mermaid-svg-szdd4Qvqkx98zlHL .cluster span{color:#333;}#mermaid-svg-szdd4Qvqkx98zlHL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-szdd4Qvqkx98zlHL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-szdd4Qvqkx98zlHL rect.text{fill:none;stroke-width:0;}#mermaid-svg-szdd4Qvqkx98zlHL .icon-shape,#mermaid-svg-szdd4Qvqkx98zlHL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-szdd4Qvqkx98zlHL .icon-shape p,#mermaid-svg-szdd4Qvqkx98zlHL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-szdd4Qvqkx98zlHL .icon-shape .label rect,#mermaid-svg-szdd4Qvqkx98zlHL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-szdd4Qvqkx98zlHL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-szdd4Qvqkx98zlHL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-szdd4Qvqkx98zlHL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTPS(加密)
加密
浏览器
服务器
HTTP(明文)
明文
明文
浏览器
服务器

TLS(Transport Layer Security)解决三件事:

威胁 TLS 解法
窃听:第三方能看到传输内容 对称加密(AES / ChaCha20)
篡改:传输途中被修改 摘要 + 签名(MAC / AEAD)
冒充:假装成服务端 服务端证书(CA 签名验证)

二、TLS 握手流程

TLS 1.3 握手只需 1-RTT(1 次往返)。下面是简化版时序:
服务器 浏览器 服务器 浏览器 #mermaid-svg-RPYlIa5bwTOV1LDx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RPYlIa5bwTOV1LDx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RPYlIa5bwTOV1LDx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RPYlIa5bwTOV1LDx .error-icon{fill:#552222;}#mermaid-svg-RPYlIa5bwTOV1LDx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RPYlIa5bwTOV1LDx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RPYlIa5bwTOV1LDx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RPYlIa5bwTOV1LDx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RPYlIa5bwTOV1LDx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RPYlIa5bwTOV1LDx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RPYlIa5bwTOV1LDx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RPYlIa5bwTOV1LDx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RPYlIa5bwTOV1LDx .marker.cross{stroke:#333333;}#mermaid-svg-RPYlIa5bwTOV1LDx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RPYlIa5bwTOV1LDx p{margin:0;}#mermaid-svg-RPYlIa5bwTOV1LDx .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RPYlIa5bwTOV1LDx text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-RPYlIa5bwTOV1LDx .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-RPYlIa5bwTOV1LDx .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-RPYlIa5bwTOV1LDx .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-RPYlIa5bwTOV1LDx .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-RPYlIa5bwTOV1LDx #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-RPYlIa5bwTOV1LDx .sequenceNumber{fill:white;}#mermaid-svg-RPYlIa5bwTOV1LDx #sequencenumber{fill:#333;}#mermaid-svg-RPYlIa5bwTOV1LDx #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-RPYlIa5bwTOV1LDx .messageText{fill:#333;stroke:none;}#mermaid-svg-RPYlIa5bwTOV1LDx .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RPYlIa5bwTOV1LDx .labelText,#mermaid-svg-RPYlIa5bwTOV1LDx .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-RPYlIa5bwTOV1LDx .loopText,#mermaid-svg-RPYlIa5bwTOV1LDx .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-RPYlIa5bwTOV1LDx .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-RPYlIa5bwTOV1LDx .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-RPYlIa5bwTOV1LDx .noteText,#mermaid-svg-RPYlIa5bwTOV1LDx .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-RPYlIa5bwTOV1LDx .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RPYlIa5bwTOV1LDx .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RPYlIa5bwTOV1LDx .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RPYlIa5bwTOV1LDx .actorPopupMenu{position:absolute;}#mermaid-svg-RPYlIa5bwTOV1LDx .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-RPYlIa5bwTOV1LDx .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RPYlIa5bwTOV1LDx .actor-man circle,#mermaid-svg-RPYlIa5bwTOV1LDx line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-RPYlIa5bwTOV1LDx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 验证证书生成 pre-master secret派生会话密钥 开始加密通信 ClientHello支持的加密套件、随机数ServerHello选定的加密套件、随机数证书链ServerKeyShareFinished(前序消息的 MAC)Finished(前序消息的 MAC)

简化解读:

  1. 客户端:发支持的 TLS 版本、加密算法列表、随机数
  2. 服务端 :选定算法、回随机数、附证书链(CA 签名过的服务端公钥)
  3. 客户端验证证书:用本地信任的 CA 公钥验证证书签名
  4. 密钥交换:客户端生成 pre-master secret,用服务端公钥加密传输;双方各自派生出会话密钥
  5. 加密通信:之后所有 HTTP 数据用会话密钥对称加密

::: tip

TLS 1.2 是 2-RTT(两次往返),TLS 1.3 优化到 1-RTT 且强制前向保密。Node 默认启用 TLS 1.3。

:::

三、证书是什么

X.509 证书本质是一段结构化数据:

复制代码
Certificate:
  Subject: CN=example.com
  Issuer: CN=Let's Encrypt R3
  Validity:
    Not Before: ...
    Not After: ...
  Subject Public Key Info:
    Public Key Algorithm: RSA / EC
    Public Key: ...
  Signature Algorithm: sha256WithRSAEncryption
  Signature Value: ...

证书里关键信息:

字段 含义
Subject 证书持有者(域名 / 组织名)
Issuer 签发机构(CA)
Validity 有效期
Public Key 服务端的公钥
Signature CA 用自己私钥对证书内容的签名

::: warning

证书 = 公钥 + 身份信息 + CA 签名 。客户端拿到证书后,用 CA 的公钥验证签名------如果通过,说明"这个公钥确实属于 example.com"。

:::

四、自签名证书:本地开发必备

生产环境证书由 CA(Let's Encrypt / DigiCert 等)签发。本地开发需要自签名证书------自己签发给自己,浏览器会报警但能用。

4.1 用 openssl 生成

bash:openssl-self-signed.sh 复制代码
# 1. 生成私钥
openssl genrsa -out server.key 2048

# 2. 生成证书签名请求(CSR)
openssl req -new -key server.key -out server.csr \
  -subj "/CN=localhost"

# 3. 用自己的私钥签发证书(365 天有效期)
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

# 4. 清理 CSR
rm server.csr

4.2 一行命令生成(开发够用)

bash:openssl-one-liner.sh 复制代码
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout server.key -out server.crt -days 365 \
  -subj "/CN=localhost"

生成的两个文件:

文件 内容
server.key 私钥(绝不能泄露
server.crt 公钥证书(可对外分发)

五、Node https 服务端

https 模块 API 与 http 几乎一致,区别只在 createServer 多传一个 options:

javascript:https-server.js 复制代码
const https = require('node:https');
const fs = require('node:fs');

const options = {
  key: fs.readFileSync('./server.key'),
  cert: fs.readFileSync('./server.crt'),
};

const server = https.createServer(options, (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end('secure hello\n');
});

server.listen(3443, () => {
  console.log('https://localhost:3443');
});
bash 复制代码
$ curl -k https://localhost:3443
secure hello
# -k 表示跳过证书验证(因为是自签名)

5.1 双向认证(mTLS)

客户端也要证书的场景(多用于服务间调用):

javascript:https-mtls.js 复制代码
const options = {
  key: fs.readFileSync('./server.key'),
  cert: fs.readFileSync('./server.crt'),
  ca: [fs.readFileSync('./client.crt')],       // 信任的客户端 CA
  requestCert: true,                            // 要求客户端证书
  rejectUnauthorized: true,                     // 证书无效则拒绝
};

六、Node https 客户端

6.1 用 https.request

javascript:https-client.js 复制代码
const https = require('node:https');

const req = https.request(
  {
    host: 'api.github.com',
    path: '/repos/nodejs/node',
    method: 'GET',
    headers: { 'User-Agent': 'node-https-client' },
  },
  (res) => {
    console.log('状态:', res.statusCode);
    let body = '';
    res.on('data', (chunk) => body += chunk);
    res.on('end', () => console.log(body.slice(0, 200)));
  }
);

req.on('error', (err) => console.error('请求失败:', err.message));
req.end();

6.2 跳过证书验证(不要在生产用)

javascript:https-insecure.js 复制代码
const req = https.request(
  {
    host: 'self-signed.example.com',
    rejectUnauthorized: false, // ⚠️ 跳过证书验证
  },
  (res) => { /* ... */ }
);

::: danger

rejectUnauthorized: false 只用于本地测试自签名证书 。生产环境关闭它意味着失去"防冒充"能力------任何伪造的 CA 都能签出"信任"的证书。

:::

6.3 自定义 CA

企业内网有自己的 CA:

javascript:https-custom-ca.js 复制代码
const options = {
  host: 'internal-api.company.com',
  ca: fs.readFileSync('./company-ca.crt'),
};

七、fetch 调用 HTTPS(Node 18+)

Node 18+ 内置的 fetch 也能直接走 HTTPS 协议------可以传自定义 https.Agent 来加 CA:

javascript:fetch-https.js 复制代码
const res = await fetch('https://api.github.com/repos/nodejs/node');
const data = await res.json();

// 自定义 CA
const agent = new https.Agent({ ca: fs.readFileSync('./company-ca.crt') });
const res2 = await fetch('https://internal-api.company.com', { agent });

八、TLS 性能与最佳实践

8.1 TLS 握手开销

每次新建 TLS 连接:

  • 1-RTT 握手延迟(~100ms 跨国)
  • 证书验证(CPU)
  • 密钥派生(CPU)

缓解方案:

  • 启用 TLS session resumption(session ticket)------ 握手过的客户端复用会话
  • HTTP/2 ------ 单连接多路复用,减少握手次数
  • keep-alive ------ 长连接复用

Node https.Agent 默认启用 session resumption。

8.2 证书有效期

Let's Encrypt 证书默认 90 天。建议自动化续期(certbot / acme.sh)。

过期后浏览器直接拦截,用户完全无法绕过(不像 HTTP 还能继续访问)。

8.3 SNI:一个 IP 多个证书

服务器可能服务多个域名:

javascript:https-sni.js 复制代码
const server = https.createServer({
  SNICallback: (servername, cb) => {
    // 根据 servername 返回不同证书
    if (servername === 'a.example.com') {
      cb(null, tls.createSecureContext({
        key: fs.readFileSync('./a.key'),
        cert: fs.readFileSync('./a.crt'),
      }));
    } else if (servername === 'b.example.com') {
      cb(null, tls.createSecureContext({
        key: fs.readFileSync('./b.key'),
        cert: fs.readFileSync('./b.crt'),
      }));
    }
  },
}, (req, res) => { /* ... */ });

servername 来自 TLS 握手的 SNI 扩展,客户端用它告诉服务端"我要访问哪个域名"。

九、生产环境 HTTPS 架构

实际项目几乎不会让 Node 直接对外暴露 HTTPS 443:
#mermaid-svg-omJHtZxifr2RHqpf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-omJHtZxifr2RHqpf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-omJHtZxifr2RHqpf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-omJHtZxifr2RHqpf .error-icon{fill:#552222;}#mermaid-svg-omJHtZxifr2RHqpf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-omJHtZxifr2RHqpf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-omJHtZxifr2RHqpf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-omJHtZxifr2RHqpf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-omJHtZxifr2RHqpf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-omJHtZxifr2RHqpf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-omJHtZxifr2RHqpf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-omJHtZxifr2RHqpf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-omJHtZxifr2RHqpf .marker.cross{stroke:#333333;}#mermaid-svg-omJHtZxifr2RHqpf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-omJHtZxifr2RHqpf p{margin:0;}#mermaid-svg-omJHtZxifr2RHqpf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-omJHtZxifr2RHqpf .cluster-label text{fill:#333;}#mermaid-svg-omJHtZxifr2RHqpf .cluster-label span{color:#333;}#mermaid-svg-omJHtZxifr2RHqpf .cluster-label span p{background-color:transparent;}#mermaid-svg-omJHtZxifr2RHqpf .label text,#mermaid-svg-omJHtZxifr2RHqpf span{fill:#333;color:#333;}#mermaid-svg-omJHtZxifr2RHqpf .node rect,#mermaid-svg-omJHtZxifr2RHqpf .node circle,#mermaid-svg-omJHtZxifr2RHqpf .node ellipse,#mermaid-svg-omJHtZxifr2RHqpf .node polygon,#mermaid-svg-omJHtZxifr2RHqpf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-omJHtZxifr2RHqpf .rough-node .label text,#mermaid-svg-omJHtZxifr2RHqpf .node .label text,#mermaid-svg-omJHtZxifr2RHqpf .image-shape .label,#mermaid-svg-omJHtZxifr2RHqpf .icon-shape .label{text-anchor:middle;}#mermaid-svg-omJHtZxifr2RHqpf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-omJHtZxifr2RHqpf .rough-node .label,#mermaid-svg-omJHtZxifr2RHqpf .node .label,#mermaid-svg-omJHtZxifr2RHqpf .image-shape .label,#mermaid-svg-omJHtZxifr2RHqpf .icon-shape .label{text-align:center;}#mermaid-svg-omJHtZxifr2RHqpf .node.clickable{cursor:pointer;}#mermaid-svg-omJHtZxifr2RHqpf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-omJHtZxifr2RHqpf .arrowheadPath{fill:#333333;}#mermaid-svg-omJHtZxifr2RHqpf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-omJHtZxifr2RHqpf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-omJHtZxifr2RHqpf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-omJHtZxifr2RHqpf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-omJHtZxifr2RHqpf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-omJHtZxifr2RHqpf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-omJHtZxifr2RHqpf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-omJHtZxifr2RHqpf .cluster text{fill:#333;}#mermaid-svg-omJHtZxifr2RHqpf .cluster span{color:#333;}#mermaid-svg-omJHtZxifr2RHqpf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-omJHtZxifr2RHqpf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-omJHtZxifr2RHqpf rect.text{fill:none;stroke-width:0;}#mermaid-svg-omJHtZxifr2RHqpf .icon-shape,#mermaid-svg-omJHtZxifr2RHqpf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-omJHtZxifr2RHqpf .icon-shape p,#mermaid-svg-omJHtZxifr2RHqpf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-omJHtZxifr2RHqpf .icon-shape .label rect,#mermaid-svg-omJHtZxifr2RHqpf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-omJHtZxifr2RHqpf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-omJHtZxifr2RHqpf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-omJHtZxifr2RHqpf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTPS
HTTP 内网
HTTP 内网
证书放在这里
浏览器
Nginx / 云 LB
Node App 1
Node App 2
Let's Encrypt 证书

理由:

  • 证书和私钥放 Nginx 比放 Node 进程更安全(不易泄露)
  • Nginx 处理 HTTPS 性能更高(epoll + 异步)
  • Node 只关心业务,无需处理 TLS 加解密

但 Node 直接对外暴露 HTTPS 仍用于:

  • 内部服务(VPN / 内网)
  • 边缘函数 / Lambda
  • 嵌入式设备
  • 一次性 demo / 调试

十、常见错误

错误 原因 解决
unable to verify the first certificate 客户端找不到 CA ca 选项,或保证系统信任链完整
certificate has expired 证书过期 续期
hostname/IP does not match certificate's altnames 域名与证书不匹配 证书 CN/SAN 要包含当前域名
self signed certificate 自签名证书未受信任 本地开发加 rejectUnauthorized: false;或把证书加入系统信任
TLS connection timeout 网络或防火墙 检查服务器监听、放行端口

十一、小结

  • HTTPS = HTTP over TLS,解决窃听 / 篡改 / 冒充三大威胁
  • TLS 1.3 握手只需 1-RTT;流程是"密钥协商 + 证书验证 + 密钥派生"
  • 证书 = 公钥 + 身份 + CA 签名;本地开发用 openssl 生成自签名证书
  • https API 与 http 一致;服务端多传 { key, cert },客户端可传 ca
  • 生产环境 HTTPS 终止通常交给 Nginx / 云负载均衡,Node 只处理 HTTP 内网流量
  • mTLS / SNI / session resumption 是高级场景,按需启用
相关推荐
AINative软件工程2 小时前
LLM Token Budget 工程实践:给每个请求设上限,让成本和质量都在掌控中
后端·llm·ai编程
卷无止境2 小时前
终端里的AI辅助,一场正在发生的编程效率变革
后端·python
程序员爱钓鱼2 小时前
Go 编程实战:Map——使用 Key-Value 管理键值数据
后端·rust·go
程序员爱钓鱼2 小时前
Rust Trait Object详解:dyn Trait与动态分发
后端·面试·rust
To_OC10 小时前
Next.js + Redis 构建笔记系统:Redis Hash 实战与性能优化
redis·后端·next.js
__zRainy__10 小时前
Node.js Web 框架选型指南:从 Express 到 Hono 的全景对比
前端·node.js·express·koa·nestjs·egg·fastify
80s77712 小时前
住宅代理解析:动态住宅IP与静态住宅IP如何选型?
网络·网络协议·tcp/ip
北斗落凡尘12 小时前
LangGraph 入门实战(11)--输出模式
后端·python·langchain
demo007x13 小时前
Hermes-Agent 技术架构
前端·后端·agent