You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js 14/TS经代理发起mTLS POST请求出现SSL报错求助

问题根因

你之前遇到的SSL错误本质是三个问题叠加:

  • tunnel库已停止维护多年,对Node.js 14的TLS逻辑兼容存在缺陷:它会把你传入的mTLS证书配置同时应用在「和代理服务器的连接」「CONNECT隧道建立后和目标服务的连接」两个环节,你的代理本身不需要客户端证书,直接会触发握手断开;同时库没有正确处理SNI、加密套件协商逻辑,才会随机抛出sslv3 alert handshake、unable to get local issuer这类无明确指向的SSL错误。
  • Node.js 14的原生TLS客户端在HTTP CONNECT代理场景下,默认不会自动给目标服务发送SNI扩展,而绝大多数启用mTLS的服务端会根据SNI返回对应域名的证书链,SNI缺失时服务端返回的证书和你本地信任的CA不匹配,就会报本地签发者校验失败——这也是你本地无代理用curl能正常连通、过代理就失败的核心差异,curl默认会自动携带SNI。
  • 你之前的axios示例里存在拼写错误:配置项baseUrl应为baseURL,不过这个不是触发SSL错误的核心原因。
符合约束的可运行实现

方案完全不依赖axios、tunnel库,仅使用Node.js原生模块+兼容Node.js 14的官方维护代理agent,纯JS实现无原生依赖,可直接打包部署到Lambda环境。

前置依赖

仅需安装兼容Node.js 14的代理agent版本,执行命令:
npm i https-proxy-agent@5.0.1

注意不要安装6.x及以上版本,高版本开始要求Node.js 16+运行环境,在Lambda Node.js 14 runtime下会直接报错。

完整TypeScript实现代码

import { HttpsProxyAgent } from 'https-proxy-agent';
import https from 'https';
import { URL } from 'url';

// 配置项建议全部存Lambda环境变量,不要硬编码到代码中
const PROXY_CONFIG = {
  host: 'proxy.test.com',
  port: 8080,
  auth: 'johndoe:fancypassword' // 无代理认证可留空
};
const TARGET_API_URL = 'https://open.supertest.com/api';
const MTLS_CERTS = {
  key: process.env.CLIENT_KEY_PEM, // 客户端私钥PEM原始文本
  cert: process.env.CLIENT_CERT_PEM, // 客户端证书PEM原始文本
  ca: process.env.CA_CHAIN_PEM // CA证书链PEM原始文本,多段证书直接拼接即可,无需拆分数组
};

async function mtlsPostOverProxy(payload: Record<string, any>) {
  // 构造带认证信息的代理地址
  const proxyUrl = new URL(`http://${PROXY_CONFIG.host}:${PROXY_CONFIG.port}`);
  if (PROXY_CONFIG.auth) {
    const [username, password] = PROXY_CONFIG.auth.split(':');
    proxyUrl.username = username;
    proxyUrl.password = password;
  }

  // 创建代理agent,注意此处不要传入任何mTLS证书配置
  const proxyAgent = new HttpsProxyAgent(proxyUrl, {
    keepAlive: true, // 复用连接降低Lambda冷启动开销
    timeout: 10000
  });

  // 核心逻辑:拦截CONNECT隧道建立事件,单独给目标服务的TLS连接配置mTLS参数
  proxyAgent.on('connect', (_req, socket: any) => {
    socket.setKeepAlive(true);
    socket.secureContext = https.createSecureContext({
      ...MTLS_CERTS,
      minVersion: 'TLSv1.2', // 和curl默认TLS版本对齐,避免版本不匹配握手失败
      ciphers: 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384' // 对齐curl默认加密套件
    });
    // 强制开启SNI,解决服务端返回错误证书导致的签发者校验失败问题
    socket.servername = new URL(TARGET_API_URL).hostname;
  });

  // 用原生https模块发请求,无需第三方HTTP客户端
  return new Promise((resolve, reject) => {
    const requestBody = JSON.stringify(payload);
    const req = https.request(TARGET_API_URL, {
      method: 'POST',
      agent: proxyAgent,
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(requestBody)
      },
      // 补一层mTLS配置兼容边缘场景
      ...MTLS_CERTS,
      servername: new URL(TARGET_API_URL).hostname,
      rejectUnauthorized: true // 生产环境绝对不要关闭证书校验
    }, (res) => {
      let responseData = '';
      res.on('data', chunk => responseData += chunk);
      res.on('end', () => {
        if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
          resolve(JSON.parse(responseData));
        } else {
          reject(new Error(`请求失败,状态码:${res.statusCode},响应内容:${responseData}`));
        }
      });
    });

    req.on('error', reject);
    req.write(requestBody);
    req.end();
  });
}

// Lambda函数入口示例
export const handler = async () => {
  try {
    const result = await mtlsPostOverProxy({ message: 'test' });
    return {
      statusCode: 200,
      body: JSON.stringify(result)
    };
  } catch (err) {
    console.error('mTLS请求失败', err);
    return {
      statusCode: 500,
      body: String(err)
    };
  }
};
排错注意事项
  • 不要把mTLS证书配置直接传入HttpsProxyAgent构造参数,否则客户端证书会被发送给不需要mTLS的代理服务器,直接触发连接断开。
  • PEM格式证书直接传原始文本即可,不要做多余的Base64转码、换行裁剪,多段CA证书直接拼接为一个字符串传入ca字段即可,无需拆分为数组。
  • 不要为了绕过错误设置rejectUnauthorized: false,该配置会跳过所有证书校验,存在中间人攻击风险,SNI和CA链配置正确的情况下不需要关闭校验即可连通。
  • 如果仍报unable to get local issuer错误,直接将本地curl测试可用的certchain.pem全量内容复制为CA配置,确保包含完整的根证书+中间证书,不要遗漏证书链节点。

内容的提问来源于stack exchange,提问作者Lumpy1201

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 06:21:47