1. 前言
开发的时候碰到了mkcert的局限性:
当我想使用不同设备(如手机、平板)通过局域网联调页面时,还是会有证书验证问题。
虽然可以在每台设备上安装根证书,但我想没有哪个人会这么做。
于是我尝试使用 域名 + 公网证书 的方式在开发环境中进行局域网调试,于是便有了这篇博文,其实只是开发的话没有必要纠结证书的问题,这篇东西实际意义比较小。
至于如何让WSL2支持LAN IP环回地址访问以及WSL2的配置,请查看我另外一篇文章:使用本机LAN IP访问WSL2中暴露的Web服务
2. 常规操作
我开发环境为WSL2+Windows,项目在WSL2内。
在WSL2内安装mkcert并生成cert与key:
# 直接从官网上面拉取,Ubuntu自己的软件源版本比较落后。curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"# 给予必要的权限chmod +x mkcert-v*-linux-amd64# 移动mkcert到/usr/local/bin/mkcert下,这样就可以直接通过mkcert命令访问sudo mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert# 在WSL2内安装根CAmkcert -install# 192.168.0.115改成你的LAN IP地址,生成cert以及key.pem文件mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1 ::1 192.168.0.115输入命令获取mkcert来查找根CA文件所在的位置:
mkcert -CAROOT取出rootCA.pem放到Windows下:
在WSL2内信任可能会有奇怪的权限问题
将文件后缀从pem改为crt,双击打开:
- 安装证书
- 本地计算机
- 将所有证书都放入下列存储
- 浏览
- 受信任的根证书颁发机构
- 确定
- 下一步
然后将cert.pem与key.pem放在该放的地方即可:
-
比如说Vite项目,在
vite.config.ts中示例配置如下:server: {port: Number(env.VITE_CLIENT_PORT) || 9090,https: {key: fs.readFileSync(path.resolve(__dirname, 'src/assets/ssl/key.pem')),cert: fs.readFileSync(path.resolve(__dirname, 'src/assets/ssl/cert.pem')),},} -
对于SpringBoot项目,在
application.yaml中示例配置如下:server:port: ${SERVER_PORT:8090}ssl:certificate: ${SSL_KEY_STORE:classpath:cert.pem}certificate-private-key: ${SSL_KEY_STORE_PWD:classpath:key.pem}
如果坚持使用
mkcert方案在其他设备上调试:生成的
rootCA.pem发送到手机上,在手机的设置 -> 安全 -> 加密与凭据 -> 安装证书 中进行信任(iOS 和 Android 步骤略有不同,自行查阅资料。)
3. 域名 + DNS 解析到内网 IP + Let’s Encrypt
这是更好的方式,直接解决你CA的问题,如果说mkcert本身仅仅是自建了一个CA自称合法机构,那么Let's Encrypt本身就是一个合法机构,但前提是你要有域名。
如何购买域名可以参考我其它文章:购买并配置 Linux VPS 以搭建个人网络服务#域名购买,这里就不多说明。
3.1 配置DNS解析
以Cloudflare为例,登录,点击左侧面板DNS-记录(record):
新建一条A记录:
- 名称:dev或者别的你喜欢的,这个其实就是子域名。
- 内容:你的
LAN IP地址。 - TTL:自动。
- 代理模式:DNS Only / 仅 DNS。如果你开启保护的话,流量会先经过
Cloudflare的服务器,但Cloudflare访问不到你本地的局域网IP,会导致访问报错。
3.2 获取 Cloudflare API Token
为了让 acme.sh 脚本能自动添加 TXT 记录来证明域名是你所有用的,需要获取API Token做授权。
- 点击右上角的用户头像 -> My Profile。
- 点击左侧 API Tokens。
- 点击 Create Token。
- 使用模板:找到 Edit zone DNS(编辑区域DNS),点击 Use template。
- Zone Resources (区域资源):
- Include -> Specific zone -> 选择域名你创建的域名的子域名。
- 点击 Continue to summary -> Create Token。
- 复制这个
Token(它只显示一次,比如 T_xxxxxxxxxxxxxxxx)。 - 同时,复制页面上的
**Account ID** (一长串数字字母组合)。
3.3 安装acme.sh脚本
# acme的前置依赖sudo apt install -y socat# acme脚本curl https://get.acme.sh | sh -s email=your@email.com# 先声明两个需要的参数export CF_Token="从Cloudflare得到的API Token"export CF_Account_ID="你从Cloudflare那边复制的AccountID"# yourdomain是你的域名,比如说test.abcde.netacme.sh --issue --dns dns_cf -d yourdomain# 等待执行,返回Cert success即为成功
# 设置你所需要的目录,以一个SpringBoot项目为例export PROJECT_PATH="/xxx"
acme.sh --install-cert -d yourdomain \--key-file $PROJECT_PATH/src/main/resources/key.pem \--fullchain-file $PROJECT_PATH/src/main/resources/cert.pem
acme.sh会记住这个位置,以后每 60 天它会自动续期证书,并自动把新文件拷贝到这里。
ok,你现在就可以使用你设置的域名去访问你的本地项目在局域网中调试。
4. 能Ping通却无法访问?
分三种情况讨论。
- 代理问题
- 路由器问题
- 防火墙问题
4.1 代理问题
因为我们使用的是Cloudflare的免费服务,如果你开着代理,它默认会匹配规则走海外线路,也就是你的代理。
为什么走了代理后就没办法正常展示你的页面?
很简单,跟你开启了Cloudflare橙云保护后反而访问不到你的LAN IP是一个道理,远端的服务器并不认识你这个LAN IP,它跟你不在一个局域网。
这种情况下我以Clash-verge为例,告诉你如何单独忽略:
- 订阅
- 右键代理配置
- 编辑文件
- rules
rules: - DOMAIN-SUFFIX,test.abcde.cfd,DIRECT #填写你实际使用的域名 - IP-CIDR,192.168.0.0/16,DIRECT,no-resolve #走本地地址时默认直连 - GEOSITE,CN,DIRECT - GEOIP,CN,DIRECT - MATCH,节点选择温馨提示:
Clash 的规则匹配机制是 从上到下,一旦匹配成功,立即停止后续匹配。
请将豁免规则写到第一条。
4.2 路由器问题
路由器有个功能叫做DNS 重绑定保护 (DNS Rebinding Protection)。
这个功能的初衷是为了安全(防止外部恶意网页攻击你的内网设备),它会拦截所有 公网域名指向局域网 IP (192.168.x.x) 的 DNS 解析结果。
不同的路由器系统设置位置不同,请根据你路由器的品牌/固件类型对号入座:
4.2.1 OpenWrt / LEDE (最常见的软路由系统)
- 网络 (Network) -> DHCP/DNS
- 常规设置 (General Settings)
- 重绑定保护 (Rebind protection)
- 两种做法
- 取消冲绑定保护
- 设置域名白名单(推荐)
- 保存并应用
4.2.2 ASUS 华硕 / 梅林 (Merlin) 固件
- 内部网络 (LAN)
- DHCP服务器
- DNS 和 WINS 服务器设置
- Enable DNS Rebind protection (开启 DNS 重绑定保护)
- 取消
- 应用
部分梅林固件支持脚本添加白名单,但操作较复杂,直接关闭最方便。
4.2.3 iKuai (爱快)
爱快默认通常不会拦截,但如果你配置了某些安全策略可能会遇到。 或者你可以利用爱快的 DNS 静态解析 功能,相当于在路由器上做了个 hosts。
- 网络设置 -> DNS 设置 -> DNS 静态解析。
- 添加:
- 域名
- IP
- 保存
这样就会直接跳过公网DNS,直接告诉设备IP,绕过重绑定检查。
4.2.4 pfSense / OPNsense
-
Services -> DNS Resolver -> General Settings.
-
Private Domains (或者 Domain Overrides)
-
Advanced Options 或者 Custom options
-
添加
server:private-domain: "xxx"
4.2.5 普通家用路由(TP-Link, 小米, 华为等)
普通家用路由器通常没有“DNS 重绑定保护”这个开关,或者默认是关闭的(即允许解析)。
一般来说看到这里你可以继续往下看了,大概率不是,但如果看完下面还是不行,建议自行设置DNS获取,看是否是默认的DNS的阻挡。
- 首选 DNS (Primary): 223.5.5.5 (阿里云 AliDNS)
- DNS Over HTTPS (DoH): https://dns.alidns.com/dns-query
- 备用 DNS (Secondary): 119.29.29.29 (腾讯云 DNSPod)
- DNS Over HTTPS (DoH): https://doh.pub/dns-query
IPV6不开
4.3 防火墙
比如说前端的8080端口你访问不了:
New-NetFirewallRule -DisplayName "Allow VITE 8080" -Direction Inbound -LocalPort 8080 -Protocol TCP -Action Allow会不会不安全?
Set-NetFirewallRule -DisplayName "Allow VITE 8080" -Profile Private这样你在公共网络下就不会开放这个8080端口啦。
5. Vite服务器不支持HMR问题
在配置了 HTTPS 和自定义域名后,控制台报错 WebSocket 连接失败。
这是因为 Vite 默认尝试连接 localhost,而浏览器当前访问的是自定义域名,导致连接被阻断。
大致报错信息如下:
client:755 WebSocket connection to 'wss://localhost:9098/?token=FRvFIXNPCDsb' failed:createConnection @ client:755connect @ client:412connect @ client:759client:765 [vite] failed to connect to websocket.your current setup:(browser) test.abcde.cfd:9098/ <--[HTTP]--> localhost:9098/ (server)(browser) test.abcde.cfd:9098/ <--[WebSocket (failing)]--> localhost:9098/ (server)Check out your Vite / network configuration and https://vite.dev/config/server-options.html#server-hmr .其实点进去你看文档说的内容就知道是怎么一回事了:
…This can be helpful when using self-signed certificates or when you want to expose Vite over a network on a single port.
在使用自签名证书或通过公网/局域网访问时,必须显式定义hmr的配置,否则 WebSocket 握手会因域名或证书不匹配而失败。
文档下面特意提到当用自签名证书或者将Vite的端口暴露在网络中访问时会对我们有所用处。
在vite.config.ts中配置如下:
server: { port: Number(env.VITE_CLIENT_PORT) || 9098, https: { key: fs.readFileSync(path.resolve(__dirname, 'src/assets/ssl/key.pem')), cert: fs.readFileSync(path.resolve(__dirname, 'src/assets/ssl/cert.pem')), }, hmr: { host: env.VITE_DOMAIN_URL, port: Number(env.VITE_CLIENT_PORT) || 9098, },},