使用 node.js/express 自动 HTTPS 连接/重定向
Posted
技术标签:
【中文标题】使用 node.js/express 自动 HTTPS 连接/重定向【英文标题】:Automatic HTTPS connection/redirect with node.js/express 【发布时间】: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)来处理这种重定向?
这是我第一次涉足网络开发,如果这是显而易见的事情,请原谅我的无知。
【问题讨论】:
这能回答你的问题吗? Heroku NodeJS http to https ssl forced redirect 【参考方案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 在另一篇文章中给出的关于安全性的重要问题的答案 - ***.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://***.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_HOSTREQUEST_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://***.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);
);
);
【讨论】:
以上是关于使用 node.js/express 自动 HTTPS 连接/重定向的主要内容,如果未能解决你的问题,请参考以下文章
Node.js /Express 和 mongoose:通过自动拉取新数据建立“可观察”的 mongodb 连接?
在 Node.js/Express 中,如何自动将此标头添加到每个“渲染”响应中?
如何从 Node.js / Express 应用程序的 Mongoose 预挂钩中查询?