【问题标题】:Automatic HTTPS connection/redirect with node.js/express使用 node.js/express 自动 HTTPS 连接/重定向
【发布时间】:2011-11-19 00:56:42
【问题描述】:

我一直在尝试使用我正在处理的 node.js 项目设置 HTTPS。对于这个例子,我基本上遵循了node.js documentation

// curl -k https://localhost:8000/
var https = require('https');
var fs = require('fs');

var options = {
  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
};

https.createServer(options, function (req, res) {
  res.writeHead(200);
  res.end("hello world\n");
}).listen(8000);

现在,当我这样做时

curl -k https://localhost:8000/

我明白了

hello world

正如预期的那样。但是如果我这样做了

curl -k http://localhost:8000/

我明白了

curl: (52) Empty reply from server

回想起来,这似乎很明显,它会以这种方式工作,但与此同时,最终访问我的项目的人不会输入 https://yadayada,我想要从他们访问网站的那一刻起,所有流量都是 https。

如何让节点(以及我正在使用的框架中的 Express)将所有传入流量移交给 https,无论是否指定它?我无法找到任何解决此问题的文档。还是只是假设在生产环境中,node 前面有一些东西(例如 nginx)来处理这种重定向?

这是我第一次涉足网络开发,如果这是显而易见的事情,请原谅我的无知。

【问题讨论】:

标签: node.js https express


【解决方案1】:

从 0.4.12 开始,我们没有真正干净的方式来使用 Node 的 HTTP/HTTPS 服务器在同一端口上侦听 HTTP 和 HTTPS。

有些人通过让 Node 的 HTTPS 服务器(也适用于 Express.js)侦听 443(或其他端口)以及绑定到 80 并将用户重定向到安全的小型 http 服务器来解决了这个问题港口。

如果您绝对必须能够在单个端口上处理这两种协议,那么您需要将 nginx、lighttpd、apache 或其他一些 Web 服务器放在该端口上,并充当 Node 的反向代理。

【讨论】:

  • 谢谢瑞恩。通过“...有一个小型 http 服务器绑定到 80 并重定向...”我假设您的意思是另一个 node http 服务器。我想我知道这可能是如何工作的,但你能指出任何示例代码吗?另外,您知道节点路线图中是否有针对此问题的“更清洁”解决方案?
  • 您的 Node.js 应用程序可以有多个 http(s) 服务器。我检查了 Node.js 问题 (github.com/joyent/node/issues) 和邮件列表 (groups.google.com/group/nodejs) 并没有看到任何问题报告,但确实在邮件列表上看到了几篇关于该问题的帖子。据我所知,这不在管道中。我建议在 github 上报告它并查看您得到的反馈。
  • 真正重要的问题(关于安全性)。在重定向实际发生之前,“攻击者”是否有可能嗅出并窃取 cookie(会话 ID)?
  • 是的,3xx 状态码和可能的 Location 标头会发送回代理,此时代理会请求 Location 标头指定的 URL。
  • 2015年了,还是这样吗?
【解决方案2】:

Ryan,感谢您为我指明了正确的方向。我用一些代码充实了你的答案(第二段),它可以工作。在这种情况下,这些代码 sn-ps 被放入我的 express 应用程序中:

// set up plain http server
var http = express();

// set up a route to redirect http to https
http.get('*', function(req, res) {  
    res.redirect('https://' + req.headers.host + req.url);

    // Or, if you don't want to automatically detect the domain name from the request header, you can hard code it:
    // res.redirect('https://example.com' + req.url);
})

// have it listen on 8080
http.listen(8080);

https express 服务器在 3000 上侦听 ATM。我设置了这些 iptables 规则,以便节点不必以 root 身份运行:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 3000

总的来说,这完全符合我的要求。

要防止通过 HTTP 窃取 cookie,请参阅 this answer(来自 cmets)或使用以下代码:

const session = require('cookie-session');
app.use(
  session({
    secret: "some secret",
    httpOnly: true,  // Don't let browser javascript access cookies.
    secure: true, // Only use cookies over https.
  })
);

【讨论】:

  • 真正重要的问题(关于安全性)。在重定向实际发生之前,攻击者是否有可能嗅出并窃取 cookie(会话 ID)?
  • 我将如何解决这个导致的症状? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
  • 其实this seems better,...只是用if(!req.secure){}包装重定向
  • 我只想指出@Costa 在另一篇文章中给出的关于安全性的重要问题的答案 - stackoverflow.com/questions/8605720/…
  • 可能想要环绕 if(req.protocol==='http') 声明
【解决方案3】:

您可以实例化 2 个 Node.js 服务器 - 一个用于 HTTP 和 HTTPS

您还可以定义两个服务器都将执行的设置函数,这样您就不必编写太多重复的代码。

这是我的做法:(使用 restify.js,但应该适用于 express.js,或者节点本身也适用)

http://qugstart.com/blog/node-js/node-js-restify-server-with-both-http-and-https/

【讨论】:

    【解决方案4】:

    使用 Nginx,您可以利用“x-forwarded-proto”标头:

    function ensureSec(req, res, next){
        if (req.headers["x-forwarded-proto"] === "https"){
           return next();
        }
        res.redirect("https://" + req.headers.host + req.url);  
    }
    

    【讨论】:

    • 我发现 req.headers["x-forwarded-proto"] === "https") 不可靠,但是 req.secure 有效!
    • 我也发现了,这个看起来很不靠谱。
    • req.secure 是正确的方法,但是如果您在代理后面,这是错误的,因为 req.secure 相当于 proto == "https",但是在代理快递后面可能会说您的 proto 是https,http
    • 您需要app.enable('trust proxy');:“表明应用程序位于前端代理之后,并使用 X-Forwarded-* 标头来确定客户端的连接和 IP 地址。” expressjs.com/en/4x/api.html#app.set
    • 不要把 url 当作字符串,使用 url 模块代替。
    【解决方案5】:

    您可以使用“net”模块在同一端口上侦听 HTTP 和 HTTPS

    var https = require('https');
    var http = require('http');
    var fs = require('fs');
    
    var net=require('net');
    var handle=net.createServer().listen(8000)
    
    var options = {
      key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
      cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
    };
    
    https.createServer(options, function (req, res) {
      res.writeHead(200);
      res.end("hello world\n");
    }).listen(handle);
    
    http.createServer(function(req,res){
      res.writeHead(200);
      res.end("hello world\n");
    }).listen(handle)
    

    【讨论】:

    • 当我在 Node 0.8 上运行它时,似乎只有最后一个调用 .listen 的服务器会应答。在这种情况下,HTTP 有效,但 HTTPS 无效。如果我颠倒 .createServer 的顺序,则 HTTP 有效,但 HTTPS 无效。 :(
    • 这不像描述的那样工作。我可以确认乔看到的问题。
    【解决方案6】:

    我发现 req.protocol 在我使用 express 时有效(没有测试过,但我怀疑它有效)。使用当前节点 0.10.22 和 express 3.4.3

    app.use(function(req,res,next) {
      if (!/https/.test(req.protocol)){
         res.redirect("https://" + req.headers.host + req.url);
      } else {
         return next();
      } 
    });
    

    【讨论】:

      【解决方案7】:

      此答案需要更新才能与 Express 4.0 一起使用。以下是我如何让单独的 http 服务器工作:

      var express = require('express');
      var http = require('http');
      var https = require('https');
      
      // Primary https app
      var app = express()
      var port = process.env.PORT || 3000;
      app.set('env', 'development');
      app.set('port', port);
      var router = express.Router();
      app.use('/', router);
      // ... other routes here
      var certOpts = {
          key: '/path/to/key.pem',
          cert: '/path/to/cert.pem'
      };
      var server = https.createServer(certOpts, app);
      server.listen(port, function(){
          console.log('Express server listening to port '+port);
      });
      
      
      // Secondary http app
      var httpApp = express();
      var httpRouter = express.Router();
      httpApp.use('*', httpRouter);
      httpRouter.get('*', function(req, res){
          var host = req.get('Host');
          // replace the port in the host
          host = host.replace(/:\d+$/, ":"+app.get('port'));
          // determine the redirect destination
          var destination = ['https://', host, req.url].join('');
          return res.redirect(destination);
      });
      var httpServer = http.createServer(httpApp);
      httpServer.listen(8080);
      

      【讨论】:

      • 我通过更改 httpApp.use('*', httpRouter);到 httpApp.use('/', httpRouter);并将其移至创建 hhtp 服务器之前的行。
      【解决方案8】:

      如果您使用常规端口,因为 HTTP 默认尝试端口 80,而 HTTPS 默认尝试端口 443,您可以简单地在同一台机器上拥有两个服务器: 代码如下:

      var https = require('https');
      
      var fs = require('fs');
      var options = {
          key: fs.readFileSync('./key.pem'),
          cert: fs.readFileSync('./cert.pem')
      };
      
      https.createServer(options, function (req, res) {
          res.end('secure!');
      }).listen(443);
      
      // Redirect from http port 80 to https
      var http = require('http');
      http.createServer(function (req, res) {
          res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
          res.end();
      }).listen(80);
      

      用 https 测试:

      $ curl https://127.0.0.1 -k
      secure!
      

      用http:

      $ curl http://127.0.0.1 -i
      HTTP/1.1 301 Moved Permanently
      Location: https://127.0.0.1/
      Date: Sun, 01 Jun 2014 06:15:16 GMT
      Connection: keep-alive
      Transfer-Encoding: chunked
      

      更多详情:Nodejs HTTP and HTTPS over same port

      【讨论】:

      • res.writeHead(301, etc.) 只会在 GET 调用中正常工作,因为301 不会告诉客户端使用相同的方法。如果您想保留使用的方法(以及所有其他参数),您必须使用res.writeHead(307, etc.)。如果它仍然不起作用,你可能不得不做一些代理。来源:http://stackoverflow.com/a/17612942/1876359
      【解决方案9】:
      var express = require('express');
      var app = express();
      
      app.get('*',function (req, res) {
          res.redirect('https://<domain>' + req.url);
      });
      
      app.listen(80);
      

      这是我们使用的,效果很好!

      【讨论】:

      • 不要把 url 当作字符串,使用 url 模块代替。
      • 这不会导致无限循环吗?
      • 不,因为它只侦听端口 80 并发送到端口 443。发生无限循环的唯一方法是,如果 443 上的某个侦听器重定向回我的代码中没有的 80。我希望这是有道理的。谢谢!
      【解决方案10】:

      这里的大多数答案建议使用 req.headers.host 标头。

      HTTP 1.1 需要 Host 标头,但它实际上是可选的,因为该标头实际上可能不是由 HTTP 客户端发送的,并且 node/express 将接受此请求。

      您可能会问:哪个 HTTP 客户端(例如:浏览器)可以发送缺少该标头的请求? HTTP 协议非常简单。您可以用几行代码制作 HTTP 请求,而不发送主机标头,并且如果每次收到格式错误的请求时都会引发异常,并且取决于您处理此类异常的方式,这可能会使您的服务器停机。

      所以始终验证所有输入。这不是妄想症,我收到的请求在我的服务中缺少主机标头。

      另外,切勿将 URL 视为字符串。使用节点 url 模块修改字符串的特定部分。可以通过多种方式利用将 URL 视为字符串。不要这样做。

      【讨论】:

      • 如果你举个例子,你的答案就完美了。
      • 听起来很合法,但一些参考/链接会很棒。
      【解决方案11】:

      如果您的应用位于受信任的代理(例如 AWS ELB 或正确配置的 nginx)之后,则此代码应该可以工作:

      app.enable('trust proxy');
      app.use(function(req, res, next) {
          if (req.secure){
              return next();
          }
          res.redirect("https://" + req.headers.host + req.url);
      });
      

      注意事项:

      • 这假设您在 80 和 443 上托管您的网站,如果不是,您需要在重定向时更改端口
      • 这还假设您要终止代理上的 SSL。如果您正在端到端进行 SSL,请使用上面@basarat 的答案。端到端 SSL 是更好的解决方案。
      • app.enable('trust proxy') 允许 express 检查 X-Forwarded-Proto 标头

      【讨论】:

        【解决方案12】:

        这对我有用:

        app.use(function(req,res,next) {
            if(req.headers["x-forwarded-proto"] == "http") {
                res.redirect("https://[your url goes here]" + req.url, next);
            } else {
                return next();
            } 
        });
        

        【讨论】:

        • 这可能是对的,但您应该详细说明这如何回答提问者的问题。
        【解决方案13】:

        我使用 Basarat 提出的解决方案,但我还需要覆盖端口,因为我曾经有 2 个不同的端口用于 HTTP 和 HTTPS 协议。

        res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(http_port,https_port) + req.url });
        

        我也更喜欢使用非标准端口,以便在没有 root 权限的情况下启动 nodejs。 我喜欢 8080 和 8443,因为我有多年的 tomcat 编程经验。

        我的完整文件变成了

        var fs = require('fs');
        var http = require('http');
        var http_port    =   process.env.PORT || 8080; 
        var app = require('express')();
        
        // HTTPS definitions
        var https = require('https');
        var https_port    =   process.env.PORT_HTTPS || 8443; 
        var options = {
           key  : fs.readFileSync('server.key'),
           cert : fs.readFileSync('server.crt')
        };
        
        app.get('/', function (req, res) {
           res.send('Hello World!');
        });
        
        https.createServer(options, app).listen(https_port, function () {
           console.log('Magic happens on port ' + https_port); 
        });
        
        // Redirect from http port to https
        http.createServer(function (req, res) {
            res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(http_port,https_port) + req.url });
            console.log("http request, will go to >> ");
            console.log("https://" + req.headers['host'].replace(http_port,https_port) + req.url );
            res.end();
        }).listen(http_port);
        

        然后我使用 iptable 在我的 HTTP 和 HTTPS 端口上处理 80 和 443 流量。

        sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
        sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 8443
        

        【讨论】:

        • 感谢洛伦佐,这对我有用。我没有使用选项,而是使用了letsencrypt。我删除了 var 选项。我添加了letsencrypt参数(privateKey、certificate、ca和credentials),并将选项更改为credentials:https.createServer(credentials,app)
        【解决方案14】:

        您可以使用express-force-https 模块:

        npm install --save express-force-https

        var express = require('express');
        var secure = require('express-force-https');
        
        var app = express();
        app.use(secure);
        

        【讨论】:

        • 请谨慎使用,因为该软件包已多年未更新。我在 AWS 中遇到了编码不好的问题。
        • 姐妹包:npmjs.com/package/express-to-https - 但我不知道它是否有效/更好/等
        • 请注意,如果您使用中间件来提供静态文件(包括单页应用程序),则任何重定向中间件都必须先附加到app。 (见this answer
        【解决方案15】:

        感谢这个人: https://www.tonyerwin.com/2014/09/redirecting-http-to-https-with-nodejs.html

        如果安全,则通过 https 请求,否则重定向到 https

        app.enable('trust proxy')
        app.use((req, res, next) => {
            req.secure ? next() : res.redirect('https://' + req.headers.host + req.url)
        })
        

        【讨论】:

        • 对于 AWS ELB 使用 app.get('X-Forwarded-Proto') != 'http' 而不是 req.secure aws.amazon.com/premiumsupport/knowledge-center/…
        • 好答案!只是使用三元表达式的改进app.use (function (req, res, next) { req.secure ? next() : res.redirect('https://' + req.headers.host + req.url) });
        • 我无法使这个解决方案工作,即使设置 IP 表路由 sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8443。接受的答案有效;记得为 cookie 设置 secure 标志。
        • 此答案仅与 Express 框架有关,如果您正在寻找纯 node.js 中的解决方案,则无济于事,这就是 OP 的代码,以及在搜索中显示此页面的原因.下面basarat的答案仍然是6年后最好的。
        • 没有设置crt和key?
        【解决方案16】:

        这对我有用:

        /* Headers */
        require('./security/Headers/HeadersOptions').Headers(app);
        
        /* Server */
        const ssl = {
            key: fs.readFileSync('security/ssl/cert.key'),
            cert: fs.readFileSync('security/ssl/cert.pem')
        };
        //https server
        https.createServer(ssl, app).listen(443, '192.168.1.2' && 443, '127.0.0.1');
        //http server
        app.listen(80, '192.168.1.2' && 80, '127.0.0.1');
        app.use(function(req, res, next) {
            if(req.secure){
                next();
            }else{
                res.redirect('https://' + req.headers.host + req.url);
            }
        });
        

        建议在重定向到 https 之前添加标头

        现在,当你这样做时:

        curl http://127.0.0.1 --include
        

        你得到:

        HTTP/1.1 302 Found
        //
        Location: https://127.0.0.1/
        Vary: Accept
        Content-Type: text/plain; charset=utf-8
        Content-Length: 40
        Date: Thu, 04 Jul 2019 09:57:34 GMT
        Connection: keep-alive
        
        Found. Redirecting to https://127.0.0.1/
        

        我使用 express 4.17.1

        【讨论】:

          【解决方案17】:

          如果您的节点应用程序安装在 IIS 上,您可以在 web.config 中这样做

          <configuration>
              <system.webServer>
          
                  <!-- indicates that the hello.js file is a node.js application 
              to be handled by the iisnode module -->
          
                  <handlers>
                      <add name="iisnode" path="src/index.js" verb="*" modules="iisnode" />
                  </handlers>
          
                  <!-- use URL rewriting to redirect the entire branch of the URL namespace
              to hello.js node.js application; for example, the following URLs will 
              all be handled by hello.js:
              
                  http://localhost/node/express/myapp/foo
                  http://localhost/node/express/myapp/bar
                  -->
                  <rewrite>
                      <rules>
                          <rule name="HTTPS force" enabled="true" stopProcessing="true">
                              <match url="(.*)" />
                              <conditions>
                                  <add input="{HTTPS}" pattern="^OFF$" />
                              </conditions>
                              <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" />
                          </rule>
                          <rule name="sendToNode">
                              <match url="/*" />
                              <action type="Rewrite" url="src/index.js" />
                          </rule>
                      </rules>
                  </rewrite>
          
                  <security>
                      <requestFiltering>
                          <hiddenSegments>
                              <add segment="node_modules" />
                          </hiddenSegments>
                      </requestFiltering>
                  </security>
          
              </system.webServer>
          </configuration>
          

          【讨论】:

            【解决方案18】:

            更新了杰克答案的代码。在您的 https 服务器旁边运行它。

            // set up plain http server
            var express = require('express');
            var app = express();
            var http = require('http');
            
            var server = http.createServer(app);
            
            // set up a route to redirect http to https
            app.get('*', function(req, res) {
              res.redirect('https://' + req.headers.host + req.url);
            })
            
            // have it listen on 80
            server.listen(80);
            

            【讨论】:

              【解决方案19】:

              这适用于我的快递:

              app.get("*",(req,res,next) => {
                  if (req.headers["x-forwarded-proto"]) {
                      res.redirect("https://" + req.headers.host + req.url)
                  }
                  if (!res.headersSent) {
                      next()
                  }
              })
              

              将此放在所有 HTTP 处理程序之前。

              【讨论】:

                【解决方案20】:

                此脚本在加载页面时会保存 URL 页面并检查地址是 https 还是 http。如果是http,脚本会自动将你重定向到同一个https页面

                (function(){
                  var link = window.location.href;
                  if(link[4] != "s"){
                    var clink = "";
                    for (let i = 4; i < link.length; i++) {
                      clink += link[i];
                    }
                    window.location.href = "https" + clink;
                  }
                })();
                

                【讨论】:

                  【解决方案21】:

                  我们的想法是检查传入的请求是否使用 https,如果是,则不要再次将其重定向到 https,而是照常继续。否则,如果是 http,则附加 https 将其重定向。

                  app.use (function (req, res, next) {
                    if (req.secure) {
                            next();
                    } else {
                            res.redirect('https://' + req.headers.host + req.url);
                    }
                  });
                  

                  【讨论】:

                    【解决方案22】:

                    从长期研究从 http 到 https 的完美重定向,我在这里找到了完美的解决方案。

                    const http = require("http");
                    const https = require("https");
                    
                    const { parse } = require("url");
                    const next = require("next");
                    const fs = require("fs");
                    
                    const ports = {
                    http: 3000,
                    https: 3001
                    }
                    
                    const dev = process.env.NODE_ENV !== "production";
                    const app = next({ dev });
                    const handle = app.getRequestHandler();
                    
                    const httpsOptions = {
                    key: fs.readFileSync("resources/certificates/localhost-key.pem"),
                    cert: fs.readFileSync("resources/certificates/localhost.pem")
                    };
                    
                    // Automatic HTTPS connection/redirect with node.js/express
                    // source: https://stackoverflow.com/questions/7450940/automatic-https- 
                    connection-redirect-with-node-js-express
                    app.prepare().then(() => {
                    
                    // Redirect from http port to https
                    http.createServer(function (req, res) {
                        res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(ports.http, ports.https) + req.url });
                        console.log("http request, will go to >> ");
                        console.log("https://" + req.headers['host'].replace(ports.http, ports.https) + req.url);
                        res.end();
                    }).listen(ports.http, (err) => {
                        if (err) throw err;
                        console.log("ready - started server on url: http://localhost:" + ports.http);
                    });
                    
                    https.createServer(httpsOptions, (req, res) => {
                        const parsedUrl = parse(req.url, true);
                        handle(req, res, parsedUrl);
                    }).listen(ports.https, (err) => {
                        if (err) throw err;
                        console.log("ready - started server on url: https://localhost:" + ports.https);
                    });
                    });
                    

                    【讨论】:

                      猜你喜欢
                      • 2019-07-17
                      • 2013-08-26
                      • 2023-03-22
                      • 2017-08-28
                      • 2014-12-03
                      • 2013-03-26
                      • 2018-02-26
                      • 2022-10-06
                      相关资源
                      最近更新 更多