使用 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 服务器作为服务

如何从 Node.js / Express 应用程序的 Mongoose 预挂钩中查询?

如何从 Node.js / Express 应用程序的 Mongoose 预挂钩中查询?

node.js--express学习之路