如何在 Express.js 中强制 SSL / https
Posted
技术标签:
【中文标题】如何在 Express.js 中强制 SSL / https【英文标题】:How to force SSL / https in Express.js 【发布时间】:2012-01-26 04:53:00 【问题描述】:我正在尝试为 Express.js 创建一个中间件,以将所有非安全(端口 80)流量重定向到安全 SSL 端口(443)。 不幸的是,Express.js 请求中没有任何信息可以让您确定请求是通过 http 还是 https。
一种解决方案是重定向每个请求,但这不是我的选择。
注意事项:
不可能用 Apache 或其他东西来处理它。 必须在节点中完成。
只有一个服务器可以在应用程序中启动。
你会怎么解决这个问题?
【问题讨论】:
可能重复***.com/questions/7450940/… 解决这个问题的方法是重定向每个请求。但就像我写的那样,这是没有选择的。 它只重定向来自http的请求。 没错,必须添加第二个注释。只允许一台服务器。 我不认为你可以在同一个套接字上说 2 个协议。我可能错了。 【参考方案1】:以防万一您在 Heroku 上托管并且只想重定向到 HTTPS 而不管端口如何,这里是我们正在使用的中间件解决方案。
如果您在本地开发,则无需重定向。
function requireHTTPS(req, res, next)
// The 'x-forwarded-proto' check is for Heroku
if (!req.secure && req.get('x-forwarded-proto') !== 'https' && process.env.NODE_ENV !== "development")
return res.redirect('https://' + req.get('host') + req.url);
next();
您可以将它与 Express(2.x 和 4.x)一起使用,如下所示:
app.use(requireHTTPS);
【讨论】:
不错的解决方案。我看到的唯一问题是它正在转发所有 http 方法,它不应该在所有没有得到的东西上返回 403。 对于我们中间的初学者:记住将app.use(requireHTTPS)
调用放在任何app.get
调用之前(在任何应该由SSL 保护的东西之前)。
完美地为我工作,谢谢!回复:@Bergur 的评论,可以在另一个中间件语句中完成。 expressjs.com/en/4x/api.html【参考方案2】:
虽然这个问题看起来已经有一年了,但我想回答一下,因为它可能对其他人有所帮助。使用最新版本的 expressjs (2.x) 实际上非常简单。首先使用此代码创建密钥和证书
openssl genrsa -out ssl-key.pem 1024
$ openssl req -new -key ssl-key.pem -out certrequest.csr
..一堆提示
$ openssl x509 -req -in certrequest.csr -signkey ssl-key.pem -out ssl-cert.pem
将证书和密钥文件存储在包含 app.js 的文件夹中。然后编辑app.js文件,在express.createServer()之前写下如下代码
var https = require('https');
var fs = require('fs');
var sslkey = fs.readFileSync('ssl-key.pem');
var sslcert = fs.readFileSync('ssl-cert.pem')
var options =
key: sslkey,
cert: sslcert
;
现在在 createServer() 函数中传递 options
对象
express.createServer(options);
完成!
【讨论】:
如果快递告诉你不要打电话给createServer
- ***.com/questions/11804202/…【参考方案3】:
首先,让我看看我是否可以澄清问题。您仅限于一 (1) 个 node.js 进程,但该进程可以侦听两 (2) 个网络端口,80 和 443,对吧? (当您说一个服务器时,不清楚您是指一个进程还是一个网络端口)。
鉴于该限制,您的问题似乎是由于您未提供的原因,您的客户端以某种方式连接到错误的端口。这是一个奇怪的边缘情况,因为默认情况下,客户端将向端口 80 发出 HTTP 请求,向端口 443 发出 HTTPS 请求。当我说“默认”时,我的意思是如果 URL 中不包含特定端口。因此,除非您明确使用像http://example.com:443 和https://example.com:80 这样的纵横交错的URL,否则您真的不应该有任何纵横交错的流量访问您的网站。但是既然你问了这个问题,我想你一定有它,虽然我敢打赌你使用的是非标准端口,而不是 80/443 默认值。
所以,作为背景:是的,一些网络服务器可以很好地处理这个问题。例如,如果您对 nginx 执行http://example.com:443,它将以 HTTP 400“错误请求”响应指示“普通 HTTP 请求已发送到 HTTPS 端口”。是的,您可以从同一个 node.js 进程同时监听 80 和 443。您只需要创建 2 个单独的 express.createServer()
实例,所以这没问题。这是一个简单的程序来演示如何处理这两种协议。
var fs = require("fs");
var express = require("express");
var http = express.createServer();
var httpsOptions =
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
;
var https = express.createServer(httpsOptions);
http.all('*', function(req, res)
console.log("HTTP: " + req.url);
return res.redirect("https://" + req.headers["host"] + req.url);
);
http.error(function(error, req, res, next)
return console.log("HTTP error " + error + ", " + req.url);
);
https.error(function(error, req, res, next)
return console.log("HTTPS error " + error + ", " + req.url);
);
https.all('*', function(req, res)
console.log("HTTPS: " + req.url);
return res.send("Hello, World!");
);
http.listen(80);
我可以像这样通过 cURL 进行测试:
$ curl --include --silent http://localhost/foo
HTTP/1.1 302 Moved Temporarily
X-Powered-By: Express
Content-Type: text/html
Location: https://localhost/foo
Connection: keep-alive
Transfer-Encoding: chunked
<p>Moved Temporarily. Redirecting to <a href="https://localhost/foo">https://localhost/foo</a></p>
$ curl --include --silent --insecure https://localhost:443/foo
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 13
Connection: keep-alive
Hello, World!%
并显示从 HTTP 到 HTTPS 的重定向...
curl --include --silent --location --insecure 'http://localhost/foo?bar=bux'
HTTP/1.1 302 Moved Temporarily
X-Powered-By: Express
Content-Type: text/html
Location: https://localhost/foo?bar=bux
Connection: keep-alive
Transfer-Encoding: chunked
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 13
Connection: keep-alive
Hello, World!%
这样可以为常规情况下的两种协议提供服务并正确重定向。然而,纵横交错根本不起作用。我相信命中快速服务器的交叉请求不会通过中间件堆栈路由,因为它从一开始就会被视为错误,甚至不会正确解析请求 URI,这是必要的通过路由中间件链发送。我认为快速堆栈甚至没有得到它们,因为它们不是有效的请求,因此它们在节点 TCP 堆栈中的某处被忽略。可能可以编写一个服务器来执行此操作,并且可能已经有一个模块,但是您必须直接在 TCP 层编写它。而且您必须在第一块客户端数据中检测到命中 TCP 端口的常规 HTTP 请求,并将该连接连接到 HTTP 服务器,而不是正常的 TLS 握手。
当我执行上述任一操作时,我的快速错误处理程序不会被调用。
curl --insecure https://localhost:80/foo
curl: (35) Unknown SSL protocol error in connection to localhost:80
curl http://localhost:443/foo
curl: (52) Empty reply from server
【讨论】:
安全问题:假设您的应用程序使用 cookie 身份验证,并且登录的人导航到网站的 http://... 部分,攻击者是否有可能在重定向之前嗅探他们的 cookie发生了什么? 是的,我相信这是可能的,这就是为什么您应该在 cookie 标头中设置“安全”标志,这将指示浏览器不要通过 HTTP 传输 cookie,而只能通过 HTTPS。 en.wikipedia.org/wiki/HTTP_cookie#Secure_and_HttpOnly 好的,太好了!只需要弄清楚如何让 CouchDB 设置一个安全标志。谢谢!! 如果快递告诉你不要打电话给createServer
- ***.com/questions/11804202/…
您能否按照 Stack 指南制作此 javascript?【参考方案4】:
基于 Elias 的回答,但使用内联代码。如果您在 nginx 或负载均衡器后面有节点,则此方法有效。 Nginx 或负载均衡器将始终使用普通的旧 http 访问节点,但它设置了一个标头以便您可以区分。
app.use(function(req, res, next)
var schema = req.headers['x-forwarded-proto'];
if (schema === 'https')
// Already https; don't do anything special.
next();
else
// Redirect to https.
res.redirect('https://' + req.headers.host + req.url);
);
【讨论】:
这样做很容易受到中间人攻击。见 HSTS:en.wikipedia.org/wiki/HTTP_Strict_Transport_Security @jonathon-tran 的答案是正确的,它不会比不这样做更容易受到中间人的攻击。 HSTS 是对您理想情况下希望与此类东西结合使用的客户端的额外保护(尽管从 IE 11 开始,IE 仍然不支持它)。 我从 chrome 收到此错误:ERR_TOO_MANY_REDIRECTS 我找到了这个答案,它奏效了:***.com/questions/10697660/force-ssl-with-expressjs-3【参考方案5】:http.createServer(app.handle).listen(80)
https.createServer(options, app.handle).listen(443)
对于快递 2x
【讨论】:
【参考方案6】:此代码看起来可以满足您的需求:https://gist.github.com/903596
【讨论】:
我试过了,但它不起作用。我得到:错误:发送后无法设置标题。并且节点进程随后死亡。【参考方案7】:试试这个例子:
var express = require('express');
var app = express();
// set up a route to redirect http to https
app.use(function (req, res, next)
if (!/https/.test(req.protocol))
res.redirect("https://" + req.headers.host + req.url);
else
return next();
);
var webServer = app.listen(port, function ()
console.log('Listening on port %d', webServer.address().port);
);
【讨论】:
【参考方案8】:由于我在使用 nginx,我可以访问标头的 x-forwarded-proto 属性,因此我可以编写一个小型中间件来重定向所有流量,如下所述:http://elias.kg/post/14971446990/force-ssl-with-express-js-on-heroku-nginx
编辑:更新网址
【讨论】:
这个页面不见了,所以我投反对票。如果它再次出现,请告诉我,以便我可以投票支持它!这可以通过复制这里的一些内容来避免。谢谢。 太棒了!改变了我的投票。 (回想起来,我可能应该只是发表评论,而不是投反对票。) 缓存副本here。或者,请参阅Jonathan Tran's answer,它实际上是帖子中代码的副本。以上是关于如何在 Express.js 中强制 SSL / https的主要内容,如果未能解决你的问题,请参考以下文章
如何在 GKE 上为 Kubernetes Ingress 强制 SSL
为啥 apache 强制执行 SSL (https)?如何撤消此操作?
如何强制使用 HTTPS(Cloudflare 灵活 SSL)?
javax.net.ssl.SSLHandshakeException:没有合适的协议。如何强制 Java 使用 TLSv1.2 发送邮件?