使用node.js / express自动HTTPS连接/重定向

Automatic HTTPS connection/redirect with node.js/express

我一直在努力通过我正在开发的node.js项目来设置HTTPS。我基本上遵循了此示例的node.js文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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
");
}).listen(8000);

现在,当我这样做

1
curl -k https://localhost:8000/

我明白了

1
hello world

正如所料。但如果我这样做

1
curl -k http://localhost:8000/

我明白了

1
curl: (52) Empty reply from server

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

我怎样才能将所有传入流量传递给https的节点(以及我正在使用的Express),无论是否指定了它?我找不到任何解决此问题的文档。或者只是假设在生产环境中,节点有一些位于它前面的东西(例如nginx)来处理这种重定向?

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


瑞恩,谢谢你指出我正确的方向。我用一些代码充实了你的答案(第2段)并且它有效。在这种情况下,这些代码片段放在我的快递应用程序中:

1
2
3
4
5
6
7
8
9
10
11
12
13
// set up plain http server
var http = express.createServer();

// 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身份运行:

1
2
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默认尝试端口80,HTTPS默认尝试端口443,您可以在同一台机器上只有两台服务器:
这是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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进行测试:

1
2
$ curl https://127.0.0.1 -k
secure!

用http:

1
2
3
4
5
6
$ 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和HTTPS在同一端口上


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

1
2
3
4
5
6
7
8
9
app.use (function (req, res, next) {
        if (req.secure) {
                // request was via https, so do no special handling
                next();
        } else {
                // request was via http, so redirect to https
                res.redirect('https://' + req.headers.host + req.url);
        }
});


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

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


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

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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);


如果您的应用程序位于受信任的代理(例如AWS ELB或正确配置的nginx)后面,则此代码应该有效:

1
2
3
4
5
6
7
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头


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

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

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

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

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

所以总是验证所有输入。这不是偏执狂,我收到的服务中缺少主机头的请求。

此外,永远不要将URL视为字符串。使用节点url模块修改字符串的特定部分。将URL作为字符串处理可以在许多方面被利用。不要这样做。


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

npm install --save express-force-https

1
2
3
4
5
var express = require('express');
var secure = require('express-force-https');

var app = express();
app.use(secure);


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
");
}).listen(handle);

http.createServer(function(req,res){
  res.writeHead(200);
  res.end("hello world
");
}).listen(handle)


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

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

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

我的完整文件成了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    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流量。

1
2
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

1
2
3
4
5
6
7
8
var express = require('express');
var app = express();

app.get('*',function (req, res) {
    res.redirect('https://<domain>' + req.url);
});

app.listen(80);

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


这对我有用:

1
2
3
4
5
6
7
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();
    }
});


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

您还可以定义两个服务器都将执行的设置功能,这样您就不必编写大量重复的代码。

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

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


这对我有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 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之前添加标头

现在,当你这样做时:

1
curl http://127.0.0.1 --include

你得到:

1
2
3
4
5
6
7
8
9
10
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/

我用快递4.17.1