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模块:
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