如何在MacOS上建立Local HTTPS Server & 使用Custom Domain存取網站 with Node.js

林罡北
13 min readJun 3, 2023

--

How to Create Local Https Server & Local Custom Domain on MacOS with Node.js

網站資安升級,如何為你的網站加上 SSL 綠色鎖頭

為什麼會有這篇文章

身為前端工程師,網頁正在開發或測試時,偶爾會有一些需求(常見的情況是整合第三方服務的時候),某個功能或是串接的服務只能在https的環境或是指定的網域下才能正常使用

導致開發時,沒辦法在local就確定服務是否有串接成功,可能必須要上alpha, beta甚至production

不只有你,我也遇到了哈哈哈
所以花一些時間整理成這篇文章
記錄一下讓自己以後能夠無腦按照文章做

誰適合這篇文章

如果你用MacOS & Node.js、不熟悉openssl,平常只會起local開發用的http web server,找了很多文章弄ssl都失敗

想要無腦的讓http前面加上s,甚至還有客製化網域
那這篇文章很適合你

這篇文章,沒有任何技術說明,全是過程
我不會跟你講什麼是openssl、什麼是Root CA
技術細節大家可以自己去Google或是看看最下面的Reference

過程中只會講解主要的流程,不會講技術細節
目標就是大家照著做,就可以有lcoal https server & custom doamin

Let’s Go!

Prepare

安裝Homebrew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

安裝openssl

brew install openssl

建立專案&對應的資料夾

project
├── server
│ ├── ssl
│ ├── index.js // entry of node.js server

Step 1. 新增openssl config

在ssl資料夾底下新增openssl.cnf檔案,直接複製貼上下面的內容
然後改一下OOUemailAddress

組織名稱跟部門名稱還有email都可以自己隨便寫,不存在也沒關係

[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[dn]
C=TW # 國別
ST=Taiwan # 國名
L=Taipei # 城市
O=CryptoInform # 組織名稱
OU=Dev # 組織底下的部門名稱
emailAddress=dev@cryptoinform.app # 聯絡用email
CN = localhost

新增完之後,資料夾結構會變這樣子:

project
├── server
│ ├── ssl
│ │ ├── openssl.cnf
│ ├── index.js // entry of node.js server

Step 2. 建立 self-signed root certificate

在ssl資料夾底下,執行下面2個指令

openssl req -x509 -nodes -new -sha256 -days 390 -newkey rsa:2048 -keyout "RootCA.key" -out "RootCA.pem" -config openssl.cnf
openssl x509 -outform pem -in "RootCA.pem" -out "RootCA.crt"

其中可以看到,第一行有一個參數是 -days 390 ,意義是390天之後會過期,因為2020/09/01之後,產生的SSL/TLS certificates的有效期限最多是397天(13個月),超過就會出現Error,所以如果要自己改,記得數字不要超過397

Detail Here: https://stackoverflow.com/a/65239775.

執行結果:

Step 2. 建立 self-signed root certificate的執行結果
Step 2. 建立 self-signed root certificate的執行結果

執行指令完之後,資料夾結構會變這樣子:

project
├── server
│ ├── ssl
│ │ ├── openssl.cnf
│ │ ├── RootCA.crt
│ │ ├── RootCA.key
│ │ ├── RootCA.pem
│ │ ├── RootCA.srl
│ ├── index.js // entry of node.js server

Step 3: 定義certificate支援的domains/subdomains

在ssl資料夾底下,新增一個vhosts_domains.ext 檔案,然後複製貼上下面的內容
然後改一下DNS.1DNS.2,有需要的話也可以新增DNS.3DNS.4 …,改成你希望支援https的domain

以下圖為例的話,這個certificate支援下面的網域

  • dev.cryptoinform.app
  • localhost

也就是說你可以透過https在local訪問這些網域!

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1=dev.cryptoinform.app # 1st domain with https
DNS.2=localhost # 2nd domain with https

執行完之後,資料夾結構會變這樣子:

project
├── server
│ ├── ssl
│ │ ├── openssl.cnf
│ │ ├── RootCA.crt
│ │ ├── RootCA.key
│ │ ├── RootCA.pem
│ │ ├── RootCA.srl
│ │ ├── vhosts_domains.ext
│ ├── index.js // entry of node.js server

Step 3. 新增certificate

一樣,在ssl資料夾底下,接著執行下面2個指令

openssl req -new -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.csr -config openssl.cnf
openssl x509 -req -sha256 -days 1024 -in localhost.csr -CA RootCA.pem -CAkey RootCA.key -CAcreateserial -extfile vhosts_domains.ext -out localhost.crt

執行結果:

Step 3. 新增certificate的執行結果
Step 3. 新增certificate的執行結果

新增完之後,資料夾結構會變這樣子:

project
├── server
│ ├── ssl
│ │ ├── localhost.crt
│ │ ├── localhost.csr
│ │ ├── localhost.key
│ │ ├── openssl.cnf
│ │ ├── RootCA.crt
│ │ ├── RootCA.key
│ │ ├── RootCA.pem
│ │ ├── RootCA.srl
│ │ ├── vhosts_domains.ext
│ ├── index.js // entry of node.js server

Step 4. 在Node.js Server中載入certificate

這是index.js 的內容
主要就是在啟動Node.js Server時載入certificate,並且把req/res轉送給Next.js處理

const https = require('https');
const next = require('next');
const fs = require('fs');

const PORT = 443
const app = next({ dev: process.env.NODE_ENV !== 'production' });
const handle = app.getRequestHandler();

const httpsOptions = {
key: fs.readFileSync('./ssl/localhost.key'),
cert: fs.readFileSync('./ssl/localhost.crt'),
};

app.prepare().then(() => {
https
.createServer(httpsOptions, (req, res) => {
handle(req, res);
})
.listen(PORT);
console.log(`> Server started on https://localhost:${PORT}`);
});

執行Server之後,你如果訪問https://localhost,你會發現你的頁面會顯示「你的連線不是私人連線」

作業系統碰到不信任的certificate時,會告知使用者這個網站不安全
作業系統碰到不信任的certificate時,會告知使用者這個網站不安全

雖然點擊「進階」再選擇「繼續前往localhost網站(不安全)」是可以成功存取網站頁面的

作業系統碰到不信任的certificate時,會告知使用者這個網站不安全,不過還是能存取網站頁面
作業系統碰到不信任的certificate時,會告知使用者這個網站不安全,不過還是能存取網站頁面

不過就會像下圖這樣,https前面多了一個「不安全」的說明文字

作業系統碰到不信任的certificate時,網域前面會顯示不安全
作業系統碰到不信任的certificate時,網域前面會顯示不安全

Step 5: 在MacOS中載入certificates

會有https前面多了一個「不安全」的說明文字,主要是因為MacOS還不知道這些憑證是不是可以信任的,所以我們這邊打開ssl資料夾,在下面2個檔案各點擊2下,打開certificates

需要加入作業系統的certificates
需要加入作業系統的certificates

打開之後會分別看到2個「加入憑證的視窗」,都直接按加入就可以

加入localhost.crt的certificate到MacOS中
加入localhost.crt的certificate到MacOS中
加入RootCA.crt的certificate到MacOS中
加入RootCA.crt的certificate到MacOS中

都加入完成之後,打開MacOS內建的「鑰匙圈存取」App

MacOS鑰匙圈存取App的圖示
MacOS鑰匙圈存取App的圖示

在左邊側欄中選擇「登入」、「憑證」,就可以在右邊列表中看到2個名為localhost的項目,圖示上一個是橘色,一個是藍色

操作鑰匙圈存取App,找到剛剛加入的certificate
操作鑰匙圈存取App,找到剛剛加入的certificate

接著像下圖雙擊點開這2個檔案

操作鑰匙圈存取App,把剛剛加入的certificate設定成永遠信任
操作鑰匙圈存取App,打開剛剛加入的certificate

接著將「使用此憑證時」改為「永遠信任」

操作鑰匙圈存取App,把剛剛加入的certificate設定成永遠信任
操作鑰匙圈存取App,把剛剛加入的certificate設定成永遠信任

然後直接關閉,系統會要求你輸入密碼確認要更改,接著回「鑰匙圈存取」App,可以看到圖示右下角多了一個「+」的符號

操作鑰匙圈存取App,如果把certificate設定成永遠信任,圖示右下角會多一個加號
操作鑰匙圈存取App,如果把certificate設定成永遠信任,圖示右下角會多一個加號

到這邊憑證的設定就基本上完成了,可以在瀏覽器中開啟https://localhost確認一下,設定正確的話就會像是下圖一樣,localhost 網域前面不會出現「不安全」的說明文字

在本地透過https://localhost拜訪網站,會發現https已經正常運作了
在本地透過https://localhost拜訪網站,會發現https已經正常運作了

Step 6: 設定custom domains

我們開啟Finder,按照下圖,選擇「前往」、「前往檔案夾」,然後在跳出來的視窗中輸入/etc/hosts 再按下前往

透過Finder前往/etc/hosts
透過Finder前往/etc/hosts

接著Finder就會幫你開啟對應的資料夾,應該就能看到hosts檔案

Finder會根據前往資料夾的路徑,打開對應的資料夾以及檔案
Finder會根據前往資料夾的路徑,打開對應的資料夾以及檔案

雙擊點開之後,檔案的內容會類似下圖,不過是無法編輯的鎖定狀態

hosts檔案的內容
hosts檔案的內容

接著我們可以按照下面這篇文章的方式複製、編輯hosts檔案
hosts 檔案中的新的一行加入127.0.0.1 add.your.domain

[教學]Mac OS X也能編輯與修改Hosts檔案方法

⚠注意:要加入的網域,要是Step 3中,有寫在DNS.1DNS.2 中的,否則會沒有辦法正常運作

新增自己的custom domain網域到hosts檔案
新增自己的custom domain網域到hosts檔案

接著我們去瀏覽器,輸入剛剛你設定好的網域,就可以在local的瀏覽器中用custom domains存取到對應的網站啦

設定好之後,就能用自己的custom domain存取網站
設定好之後,就能用自己的custom domain存取網站

可喜可賀,打完收工!

Reference

--

--