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


180

我一直在尝试通过正在处理的node.js项目来设置HTTPS。对于该示例,我基本上遵循了node.js文档

// 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,而不管是否已指定?我还没有找到解决此问题的任何文档。还是只是假设在生产环境中,节点位于它前面的东西(例如nginx)可以处理这种重定向?

这是我第一次涉足Web开发,所以如果这很明显,请原谅我的无知。


在这里简洁地回答了这个问题:stackoverflow.com/a/23894573/1882064
arcseldon 2014年

3
对于部署到HEROKU的任何人,此问题的答案都不会帮助您(您将获得“重定向过多”),但另一个问题的答案将是
Rico Kahler

Answers:


178

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

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

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

总之,这完全符合我的期望。


15
真正重要的问题(关于安全性)。在实际发生重定向之前,攻击者是否有可能嗅出并窃取cookie(会话ID)?
2012年

4
我该如何解决这种症状?Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
bodine

16
其实,这似乎更好,...只需将重定向包裹在if(!req.secure){}
bodine

6
我只是想指出的答案@哥斯达黎加的有关安全性的重要问题在另一篇文章给出- stackoverflow.com/questions/8605720/...
ThisClark

2
可能想包装一下if(req.protocol==='http')声明
2016年

120

如果您遵循常规端口,因为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和HTTPS通过同一端口


4
res.writeHead(301, etc.)因为301不会告诉客户端使用相同的方法,所以只能对GET调用正常工作。如果要保留使用的方法(以及所有其他参数),则必须使用res.writeHead(307, etc.)。如果仍然无法使用,则可能需要进行一些代理。来源:http
//stackoverflow.com/a/17612942/1876359

112

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

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);
        }
});

10
这是最好的答案!
斯特凡

3
同意,应该是第一个答案。
1984年

12
以下行帮助此解决方案为我工作: app.enable('trust proxy');
arbel03 '18 -10-2

2
如上所述,^^^^:如果您在重定向流量的任何类型的代理,防火墙或负载均衡器后面,则需要“信任代理”:例如,AWS负载均衡会将所有http和https请求发送到同一非根端口(例如3000)在您的VPC的网络服务器上。
alanwaring

很好的答案,我只是想知道如何检查非www网址并重定向到www
Richlewis19年

25

使用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);  
}

5
我发现req.headers [“ x-forwarded-proto”] ===“ https”)不可靠,但是req.secure可以!
Zugwalt

我发现相同,这看起来非常不可靠。
2013年

2
req.secure是正确的方法,但是如果您在代理后面,则这是有问题的,因为req.secure等效于proto ==“ https”,但是在代理快递后面可能会说您的proto是https,http
dacopenhagen

7
您需要app.enable('trust proxy');:“指示该应用程序位于前端代理之后,并使用X-Forwarded- *标头确定客户端的连接和IP地址。” expressjs.com/en/4x/api.html#app.set
dskrvk

不要将url视为字符串,而应使用url模块。
arboreal84

12

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

某些人通过让Node的HTTPS服务器(也可以与Express.js一起使用)侦听443(或其他端口),并且使小型http服务器绑定到80并将用户重定向到安全端口,从而解决了此问题。

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


谢谢瑞安。通过“ ...将一个小型http服务器绑定到80并重定向...”,我假设您的意思是另一个节点 http服务器。我想我对此有一个想法,但是您可以指向任何示例代码吗?另外,您知道节点路线图中是否存在针对此问题的“更清洁”的解决方案?
杰克

您的Node.js应用程序可以具有多个http(s)服务器。我检查了Node.js问题(github.com/joyent/node/issues)和邮件列表(groups.google.com/group/nodejs),没有看到任何报告的问题,但是确实看到了有关该问题的几篇文章在邮件列表上。据我所知,这不在管道中。我建议在github上报告它,并查看得到的反馈。
瑞安·奥尔兹

真正重要的问题(关于安全性)。在实际发生重定向之前,“攻击者”是否有可能嗅出并窃取cookie(会话ID)?
2012年

是的,3xx状态码和可能的位置标头会发回给代理,此时代理会请求位置标头指定的URL。
瑞安·奥尔兹

是2015年,情况仍然如此吗?
TheEnvironmentalist

10

您可以使用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);

1
请谨慎使用此软件包,因为该软件包已多年未更新。我在AWS中遇到编码不好的问题。
Martavis P.

1
姐妹包:npmjs.com/package/express-to-https-但我不知道它是否有效/
更好

请注意,如果您使用中间件来提供静态文件(包括单页应用程序),则必须首先将任何重定向中间件附加到该中间件app。(请参阅此答案
马修·R。

9

我使用了Basarat提出的解决方案,但是我还需要覆盖端口,因为我以前有两个用于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,证书,ca和凭据),然后将选项更改为凭据:https.createServer(credentials,app)
Nhon Ha

8

该答案需要进行更新才能与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);

1
我通过更改httpApp.use('*',httpRouter);使它起作用 到httpApp.use('/',httpRouter); 并在创建hhtp服务器之前将其移至该行。
KungWaz 2014年

7

如果您的应用程序位于受信任的代理(例如,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标头

6

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

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

5

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

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

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

因此,请始终验证所有输入。这不是偏执狂,我收到的请求中缺少主机标头。

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


如果您举了一个例子,您的答案将是完美的。
SerG '18 -10-27

听起来合法,但一些参考/链接会很棒。
Giwan

3
var express = require('express');
var app = express();

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

app.listen(80);

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


1
不要将url视为字符串,而应使用url模块。
arboreal84

4
举一个带有责任感的例子。堆栈溢出是电话游戏。
arboreal84

1
这不会导致无限循环吗?
Muhammad Umer

不,因为它仅侦听端口80并发送到端口443。无限循环的唯一方法是,如果443上的某些侦听器重定向回我的代码中没有的80。我希望这是有道理的。谢谢!
尼克·科腾伯格

3

您可以使用“ 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)

5
当我在Node 0.8上运行此命令时,只有最后一个调用.listen的服务器似乎可以回答。在这种情况下,HTTP有效,但HTTPS不起作用。如果我颠倒.createServer的顺序,则HTTP可以工作,但HTTPS不能。:(

1
这不能按所述方式工作。我可以确认乔看到的问题。
Stepan Mazurov 2013年

2

这对我有用:

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();
    } 
});

这可能是正确的,但是您应该详细说明这如何回答提问者的问题。
乔C


0

这对我有用:

/* 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/

我用快递4.17.1

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.