【问题标题】:multiple certificates/vhosts with ExpressExpress 的多个证书/虚拟主机
【发布时间】:2016-07-27 12:30:27
【问题描述】:

我现在在多个主机上使用 nodejs 有一段时间了,在 Cody-CMS 中使用 Express + vhost。现在我还想包括虚拟 https 服务器。

SNICallback 被调用,但它在那里结束......我的 Express 应用程序“exp”从未被调用(即使我在 createServer 中用一个简单的函数替换它——在 cmets 中)。我收到“请求:site1.com”(或 site2.com),但浏览器没有返回任何内容。

对于 http 服务器来说,它可以完美运行。

欢迎任何帮助。

"use strict";

var express = require("express");
var vhost = require("vhost");
var fs = require("fs");


var app1 = express();
app1.all("/", function(req, res) {
  res.send('Hello World, site #1');
});

var app2 = express();
app2.all("/", function(req, res) {
  res.send('Hello World, site #2');
});


//////////////////
// certificates //
//////////////////
var crypto = require('crypto');

const site1 = {
  app: app1,
  context: crypto.createCredentials({
    key: fs.readFileSync('ws.key').toString(),
    cert: fs.readFileSync('ws.crt').toString()
  }).context

};
const site2 = {
  app: app2,
  context: crypto.createCredentials({
    key: fs.readFileSync('ws2.key').toString(),
    cert: fs.readFileSync('ws2.crt').toString()
  }).context
};

var sites = {
  "www.site1.com": site1,
  "site1.com": site1,

  "www.site2.com": site2,
  "site2.com": site2
};

// put (www.)site1/2.com in /etc/hosts to 127.0.0.1



//////////
// http //
//////////

var exp = express();
for (let s in sites) {
  console.log("http -> " + s);
  exp.use(vhost(s, sites[s].app));
}

exp.listen(80, function () {
   console.log("Listening https on port: 80")
});


///////////
// https //
///////////

var secureOpts = {
  SNICallback: function (domain) {
    console.log('request for: ', domain);
    return sites[domain].context;
  },
  key: fs.readFileSync('ws.key').toString(),
  cert: fs.readFileSync('ws.crt').toString()
};


var https = require('https');
var httpsServer = https.createServer(secureOpts, exp);
// var httpsServer = https.createServer(secureOpts, function(req, resp) { resp.send("hello"); });

httpsServer.listen(443, function () {
   console.log("Listening https on port: 443")
});

【问题讨论】:

    标签: node.js express https virtualhost


    【解决方案1】:

    SNICallback 有第二个参数:cbcb 的签名为 (error, context)。所以你的secureOpts 应该是这样的:

    var secureOpts = {
      SNICallback: function(domain, cb) {
        console.log('request for: ', domain);
        cb(null, sites[domain].context);
      },
      key: fs.readFileSync('ws.key').toString(),
      cert: fs.readFileSync('ws.crt').toString()
    };
    

    【讨论】:

    • 当我收到一个我没有凭据的域时,我应该怎么做?
    • 您可以将错误对象传递给cb 作为第一个参数,而不是像示例中的null
    【解决方案2】:

    在 mscdex 的帮助下,我使示例正常工作。 http://coppieters.blogspot.be/2016/04/nodejs-virtual-hosts-websitesapps-on.html

    【讨论】:

      【解决方案3】:

      您也可以使用 tls.createSecureContext 而不使用 crypto.createCreadentials

      这里是我的例子:

      const https = require("https");
      const tls = require("tls");
      
      const certs = {
          "localhost": {
              key: "./certs/localhost.key",
              cert: "./certs/localhost.crt",
          },
          "example.com": {
              key: "./certs/example.key",
              cert: "./certs/example.cert",
              ca: "./certs/example.ca",
          },
      } 
      
      function getSecureContexts(certs) {
      
          if (!certs || Object.keys(certs).length === 0) {
            throw new Error("Any certificate wasn't found.");
          }
      
          const certsToReturn = {};
      
          for (const serverName of Object.keys(certs)) {
            const appCert = certs[serverName];
      
            certsToReturn[serverName] = tls.createSecureContext({
              key: fs.readFileSync(appCert.key),
              cert: fs.readFileSync(appCert.cert),
              // If the 'ca' option is not given, then node.js will use the default
              ca: appCert.ca ? sslCADecode(
                fs.readFileSync(appCert.ca, "utf8"),
              ) : null,
            });
          }
      
          return certsToReturn;
      }
      
      // if CA contains more certificates it will be parsed to array
      function sslCADecode(source) {
      
          if (!source || typeof (source) !== "string") {
              return [];
          }
      
          return source.split(/-----END CERTIFICATE-----[\s\n]+-----BEGIN CERTIFICATE-----/)
              .map((value, index: number, array) => {
              if (index) {
                  value = "-----BEGIN CERTIFICATE-----" + value;
              }
              if (index !== array.length - 1) {
                  value = value + "-----END CERTIFICATE-----";
              }
              value = value.replace(/^\n+/, "").replace(/\n+$/, "");
              return value;
          });
      }
      
      const secureContexts = getSecureContexts(certs)
      
      const options = {
          // A function that will be called if the client supports SNI TLS extension.
          SNICallback: (servername, cb) => {
      
              const ctx = secureContexts[servername];
      
              if (!ctx) {
                  log.debug(`Not found SSL certificate for host: ${servername}`);
              } else {
                  log.debug(`SSL certificate has been found and assigned to ${servername}`);
              }
      
              if (cb) {
                  cb(null, ctx);
              } else {
                  return ctx;
              }
          },
      };
      
      
      var https = require('https');
      var httpsServer = https.createServer(options, (req, res) => { console.log(res, req)});
      httpsServer.listen(443, function () {
         console.log("Listening https on port: 443")
      });
      

      如果你想测试它:

      1. 编辑 /etc/hosts 并添加记录 127.0.0.1 example.com

      2. 用网址https://example.com:443打开浏览器

      【讨论】:

        猜你喜欢
        • 2017-04-07
        • 2011-01-31
        • 2016-11-05
        • 1970-01-01
        • 2019-04-21
        • 2020-04-29
        • 2013-01-27
        • 2018-08-24
        • 1970-01-01
        相关资源
        最近更新 更多