# HTTPS
30 秒速记
HTTPS=HTTP+TLS:先握手确认对方身份、协商出一把对称密钥,后面的数据用这把密钥加密- 证书 = 服务器公钥 + 域名等身份信息 + 上级
CA的签名;客户端顺着证书链验到本地信任的根证书才放行 - 现在主流是
ECDHE密钥交换,证书的私钥只用来签名证明「我是我」,不直接加密会话密钥 Node里https.createServer({ key, cert }, app)就能直接起服务;更常见是nginx终止TLS后转HTTP给Node- 线上关注:至少
TLS 1.2、证书链发完整、SAN写全域名、到期前自动续签
HTTPS 就是在 HTTP 下面垫了一层 TLS,先确认「对面真的是这个网站」,再协商一把只有双方知道的密钥加密通信。 身份靠证书:网站把证书发过来,客户端检查它是不是由自己信任的 CA 签的、域名对不对、有没有过期。密钥协商现在基本是 ECDHE,双方各出一部分随机数算出同一把对称密钥,后续数据都用它加密,所以即便将来服务器私钥泄露,以前录下来的流量也解不开。Node 可以自己处理 TLS,但线上我更习惯让 nginx 或网关去做,证书集中管理。
理解「HTTPS」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
面试官追问
追问 1既然有非对称加密,为什么数据传输还要用对称加密?
非对称加密慢好几个数量级,只适合握手阶段做身份认证和密钥协商。协商出对称密钥之后,大量数据用 AES 这类算法加解密,又快又安全。
追问 2Node 里请求一个 HTTPS 接口报错,后端日志完全没有这个请求,说明什么?
说明请求死在了 TLS 握手阶段,还没到业务代码。看错误对象的 code,比如 CERT_HAS_EXPIRED、SELF_SIGNED_CERT_IN_CHAIN、ERR_TLS_CERT_ALTNAME_INVALID,基本就能定位是过期、信任链还是域名不匹配。
追问 3TLS 交给 nginx 终止后,Node 里 req.protocol 总是 http,怎么办?
nginx 到 Node 这段确实是 HTTP。让 nginx 加 X-Forwarded-Proto 头,Express 里设 app.set('trust proxy', 1),只信任最近一层代理,req.protocol 和 req.secure 就对了。
追问 4证书突然过期导致全站访问失败,怎么避免下次再发生?
用 Let's Encrypt + certbot 或 acme.sh 自动续签,免费证书有效期 90 天,脚本会提前续。再加一个监控,证书剩余天数低于 14 天就告警,自动续签挂了也能提前知道。
# 客户端例子:不受信任的证书
30 秒速记
- 浏览器报「不是私密连接」和
Node报SELF_SIGNED_CERT_IN_CHAIN是同一件事:证书链验不到本地信任的根 - 常见原因:自签名证书、私有
CA、服务端漏发中间证书、公司代理替换了证书 SELF_SIGNED_CERT_IN_CHAIN= 链里有个自签名根但不受信任;DEPTH_ZERO_SELF_SIGNED_CERT= 证书本身就是自签名- 正确解法:
https.get(url, { ca })指定根证书,或启动时设NODE_EXTRA_CA_CERTS=./ca.pem rejectUnauthorized: false/NODE_TLS_REJECT_UNAUTHORIZED=0等于关掉身份校验,只能临时本地调试
Node 默认会校验证书,证书链对不上本地信任的根证书,握手直接失败,请求根本发不出去。 浏览器遇到这种情况会弹一个警告页让人决定,Node 没有人可以问,就直接抛错,比如 SELF_SIGNED_CERT_IN_CHAIN。处理思路和浏览器「导入根证书」一样:把这个根证书告诉 Node,用请求选项里的 ca,或者环境变量 NODE_EXTRA_CA_CERTS。网上很多答案教你设 rejectUnauthorized: false,那等于把门锁拆了,中间人随便冒充,生产代码里见到我会直接拦下来。
我们知道,有些网站的HTTPS证书会被浏览器标识为不受信任,有可能是以下情况导致的:
- 颁发证书的机构不在操作系统的受信列表里
- 办法证书的机构在操作系统的受信列表里,但证书的安全级别不够
比如我们访问12306,chrome就会提示你“您的连接不是私密连接,攻击者可能会试图从kyfw.12306.cn窃取您的信息”。
那么,当我们用node向12306发起请求时,又会是什么状况呢?下面就来试下
var https = require('https');
https.get('https://www.baidu.com', function(res){
res.on('data', function(data){
process.stdout.write(data);
});
}).on('error', function(err){
console.error(err);
});
运行上面代码,输出如下。可以看到出现报错,提示信息是“self signed certificate in certificate chain”。大意就是说证书是网站自己签发的,不安全。
{ Error: self signed certificate in certificate chain
at Error (native)
at TLSSocket.<anonymous> (_tls_wrap.js:1055:38)
at emitNone (events.js:86:13)
at TLSSocket.emit (events.js:185:7)
at TLSSocket._finishInit (_tls_wrap.js:580:8)
at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:412:38) code: 'SELF_SIGNED_CERT_IN_CHAIN' }
出现上述错误怎么处理呢?我们知道,如果是在浏览器里访问,有两种处理方式:
- 忽略浏览器的安全提示,继续访问(浏览器可能会直接禁止你访问)
- 将网站的根证书导入到操作系统的受信任根证书列表里
面试官追问
追问 1报 SELF_SIGNED_CERT_IN_CHAIN,同事直接加了 NODE_TLS_REJECT_UNAUTHORIZED=0,能接受吗?
不接受。这个变量对整个进程生效,所有 HTTPS 请求都不再校验证书,被中间人劫持也发现不了,Node 自己都会打一条警告。正确做法是拿到对方的根证书,用 ca 选项或 NODE_EXTRA_CA_CERTS 加进信任列表。
追问 2内网服务用的是公司私有 CA 签的证书,Node 客户端怎么配?
只影响某个请求就用 https.get(url, { ca: fs.readFileSync('company-ca.pem') });整个进程都要信任就启动时设 NODE_EXTRA_CA_CERTS=/path/company-ca.pem。后者是追加到内置根证书列表,不会让公网证书失效。
追问 3同一个 URL,本机请求正常,CI 里报证书错误,先查什么?
Node 用的是自己内置的一份根证书列表,不读系统钥匙串。先看本机是不是设了 NODE_EXTRA_CA_CERTS 而 CI 没设;再看 CI 是不是走了公司代理,代理把证书换成了自己的。
追问 4浏览器能正常打开,Node 却报 unable to verify the first certificate,为什么?
大概率是服务器没把中间证书发全。浏览器会缓存或自动下载中间证书,Node 不会。用 openssl s_client -connect host:443 -showcerts 看服务端实际发了几张证书,把中间证书拼进服务端的证书文件就好了。
# 入门示例
30 秒速记
- 最小服务端:
https.createServer({ key, cert }, handler).listen(443),比http多一个证书参数 key是私钥,cert是证书;有中间证书时把它们按顺序拼进cert- 和
Express配合:https.createServer(options, app),app本身就是一个handler - 客户端:
https.get/fetch,自签名证书用ca指定信任的根证书 - 本地开发推荐
mkcert一条命令生成受信任的localhost证书
Node 起 HTTPS 服务和起 HTTP 服务几乎一样,只是多传一个包含私钥和证书的配置对象。 https.createServer 的第二个参数和 http.createServer 完全相同,所以 Express 的 app 直接传进去就能用。真正要花心思的是证书从哪来:公网用 CA 签发的,本地开发用 mkcert 生成并让系统信任,测试自签名证书时客户端要用 ca 选项告诉 Node
