Node 应用本地 HTTPS

为了方便做一些模拟验证,我们有时候需要在本地起 https 环境,比如调试验证一些协议是 https 的内嵌页面。

实现自签名证书

最先想到的方法当然是自签名证书,自己签发证书和密钥之后,启动一个 https 服务即可。这块网上资料比较杂乱,下面记录一下过程。

安装 openssl

首先本地要安装一下 openssl,用来生成证书。mac 上的话 homebrew 安装即可,我本地安装过了,查看一下版本:

Bash
┌─(~)─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────(albert:s004)─┐
└─(14:31:29)──> openssl version                                                                                                                                                         ──(三, 309)─┘
LibreSSL 2.8.3

先用 openssl 生成私钥

Bash
┌─(~/Documents/Code)──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────(albert:s002)─┐
└─(16:14:47)──> openssl genrsa -out server.key 1024                                                                                                                                     ──(五, 211)─┘
Generating RSA private key, 1024 bit long modulus
....++++++
.............++++++
e is 65537 (0x10001)

可以看到 server.key已经有了:

Bash
┌─(~/Documents/Code)──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────(albert:s002)─┐
└─(16:15:08)──> ll                                                                                                                                                                      ──(五, 211)─┘
total 8
drwxr-xr-x  11 albert  staff   352B  2  8 14:03 code
drwxr-xr-x   8 albert  staff   256B  2 11 14:51 demo
drwxr-xr-x   4 albert  staff   128B  1 19 17:00 github
drwxr-xr-x   3 albert  staff    96B 12 20 10:57 gitlab
-rw-r--r--   1 albert  staff   887B  2 11 16:15 server.key

再用私钥生成证书申请文件

Bash
┌─(~/Documents/Code)──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────(albert:s002)─┐
└─(16:15:13)──> openssl req -new -key server.key -out server.csr                                                                                                                        ──(五, 211)─┘
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:CN
State or Province Name (full name) []:Shanghai
Locality Name (eg, city) []:Shanghai
Organization Name (eg, company) []:Company
Organizational Unit Name (eg, section) []:Company
Common Name (eg, fully qualified host name) []:*.localhttps.com
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:

req 的时候需要根据命令行提示输入必要信息,前几个可以一路回车,直到 Common Name这里需要输入 localhost,也可以天 *.domain.com 申请通配符域名证书。最后的 password也可以放空。

执行完之后可以看到申请文件  server.csr已经生成:

Bash
┌─(~/Documents/Code)──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────(albert:s002)─┐
└─(16:18:02)──> ll                                                                                                                                                                      ──(五, 211)─┘
total 16
drwxr-xr-x  11 albert  staff   352B  2  8 14:03 code
drwxr-xr-x   8 albert  staff   256B  2 11 14:51 demo
drwxr-xr-x   4 albert  staff   128B  1 19 17:00 github
drwxr-xr-x   3 albert  staff    96B 12 20 10:57 gitlab
-rw-r--r--   1 albert  staff   668B  2 11 16:18 server.csr
-rw-r--r--   1 albert  staff   887B  2 11 16:15 server.key

用私钥对申请文件签名从而生成证书

Bash
┌─(~/Documents/Code)──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────(albert:s002)─┐
└─(16:18:05)──> openssl x509 -req -in server.csr -out server.crt -signkey server.key -days 3650                                                                                         ──(五, 211)─┘
Signature ok
subject=/C=CN/ST=Shanghai/L=Shanghai/O=Company/OU=Company/CN=*.localhttps.com
Getting Private key

这里可以通过-days设置有效期,比如我这里生成了一个十年有效的 server.crt

Bash
┌─(~/Documents/Code)──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────(albert:s002)─┐
└─(16:18:19)──> ll                                                                                                                                                                      ──(五, 211)─┘
total 24
drwxr-xr-x  11 albert  staff   352B  2  8 14:03 code
drwxr-xr-x   8 albert  staff   256B  2 11 14:51 demo
drwxr-xr-x   4 albert  staff   128B  1 19 17:00 github
drwxr-xr-x   3 albert  staff    96B 12 20 10:57 gitlab
-rw-r--r--   1 albert  staff   883B  2 11 16:18 server.crt
-rw-r--r--   1 albert  staff   668B  2 11 16:18 server.csr
-rw-r--r--   1 albert  staff   887B  2 11 16:15 server.key

nodejs 起 https 服务

由于要响应静态资源,并且起 https 服务的时候我不想再写端口,所以比起 https.createServer我们还要再多写点代码。
方便起见,我就用 koa 了,先起一个大家经常写的 http 服务:

Javascript
const path = require('path');
const fs = require('fs');
const Koa = require('koa');
const BodyParser = require('koa-bodyparser');
const serve = require('koa-static');
const mount = require('koa-mount');
const cors = require('@koa/cors');
const Router = require('@koa/router');

const PORT = 3002;

const sdkConfig = {...};

const app = new Koa();
const static_pages = new Koa();
static_pages.use(serve(__dirname + '/build'));
app.use(mount('/', static_pages));

app.use(BodyParser());
app.use(cors());

const router = new Router();

router.get('/api/data',async (ctx, next)=>{
  ctx.status = 200;
  ctx.body = { data: 1 };
  await next();
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(PORT, function () {
  console.log("==> Listening on port %s. Visit http://localhost:%s/", PORT, PORT);
});

再把他改造成 https 服务:

Diff
+ const http = require('http');
+ const https = require('https');
const path = require('path');
const fs = require('fs');
const Koa = require('koa');
const BodyParser = require('koa-bodyparser');
const serve = require('koa-static');
const mount = require('koa-mount');
const cors = require('@koa/cors');
const Router = require('@koa/router');

const PORT = 3002;

const sdkConfig = {...};

const app = new Koa();
const static_pages = new Koa();
static_pages.use(serve(__dirname + '/build'));
app.use(mount('/', static_pages));

app.use(BodyParser());
app.use(cors());

const router = new Router();

router.get('/api/data',async (ctx, next)=>{
  ctx.status = 200;
  ctx.body = { data: 1 };
  await next();
});

app.use(router.routes()).use(router.allowedMethods());

- app.listen(PORT, function () {
-  console.log("==> Listening on port %s. Visit http://localhost:%s/", PORT, PORT);
- });

+ http.createServer(app.callback()).listen(PORT);

+ const options = {
+   key: fs.readFileSync('./config/server.key', 'utf-8'),
+   cert: fs.readFileSync('./config/server.cert', 'utf-8'),  
+ };

+ https.createServer(options, app.callback()).listen(443, () => {
+   console.log('==> Listening on port 443. Visit https://localhost');
+ });

相比之前这里稍作了改动。先照常启动一个 http 服务,把之前生成的签名证书和密钥读取之后,在启动一个相同的 https server 监听 443端口。这样就可以通过 https://localhost ,不用加端口直接访问服务了。

浏览器查看

直接用 chrome 访问试试,大概率会显示 SSL error: ERR_CERT_INVALID,老版本 chrome 可以选择跳过,新版 chrome 对这方面限制的并且无法跳过。

  • 解法一是去 chrome://flags/#allow-insecure-localhost开启允许无效证书选项。
  • 解法二是在当前提示错误的页面,输入 thisisunsafe告诉 chrome 你知道的,不用管。

我比较喜欢解法二。 image.png