diff --git a/deploy/wechat-token-zhongkong.md b/deploy/wechat-token-zhongkong.md index 3d5227c..bb38937 100644 --- a/deploy/wechat-token-zhongkong.md +++ b/deploy/wechat-token-zhongkong.md @@ -1,97 +1,70 @@ -# 微信 access_token 中控(腾讯云静态 IP 换 token,经 frp 隧道拉取) +# 微信 access_token 中控(腾讯云静态 IP 换 token) ## 为什么 -gateway 换 access_token 时微信看到的是**本地宽带出网 IP(106.58.232.43,动态会变)**, -一变白名单就失效、二维码建不出来(40164)。微信官方推荐「中控服务器」:一台**固定 IP** -的机器统一换 token,业务机拉取使用。腾讯云那台(`162.14.122.200`,静态)当中控。 +gateway 换 access_token 时微信看到的是**本地宽带出网 IP(动态会变)**,一变白名单 +就失效、二维码建不出来(40164)。微信官方推荐「中控服务器」:一台**固定 IP** 的机器 +统一换 token,业务机拉取使用。腾讯云那台(`162.14.122.200`,静态)当中控。 微信 IP 白名单**只限制换 token 这一步**(用 appsecret),拿 token 建二维码不查 IP。 -所以:腾讯云换 token → gateway 经 frp 隧道拉 token → gateway 本地建二维码。 -白名单只填腾讯云 IP,永不失效。 +所以:腾讯云换 token → gateway 拉 token → gateway 本地建二维码。白名单只填腾讯云 IP。 -token 全程**不上公网**:腾讯云 token 服务只绑 127.0.0.1,靠 frp stcp(点对点加密隧道) -让 132 访问。frp 拓扑:腾讯云=frps、132=frpc,均 **toml** 配置。 +## 实际采用方案(B:宝塔 nginx + IP + 密钥) -## 一、腾讯云侧(frps 那台) +腾讯云未配域名,用 IP 直接 serve token.json,加密钥头保护。token 走公网明文传, +但强随机密钥保护 + 90 分钟轮换,风险可控(单产品早期可接受;要更严可后续换 frp 隧道)。 -### 1. 换 token 脚本 `/home/workspace/wxtoken/refresh.sh` -```bash -#!/bin/bash -APPID="wxc236cddde8e7f863" -SECRET="你的公众号 AppSecret" # 建议重置后填新的 -OUT="/home/workspace/wxtoken/token.json" -resp=$(curl -s "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${SECRET}") -# 只在拿到 access_token 时才覆盖,避免偶发失败把好文件冲掉 -echo "$resp" | grep -q '"access_token"' && echo "$resp" > "$OUT.tmp" && mv "$OUT.tmp" "$OUT" +### 腾讯云(宝塔) + +1. **计划任务**(宝塔「计划任务」→ 添加,Shell 脚本,每 90 分钟): + ```bash + #!/bin/bash + APPID="wxc236cddde8e7f863" + SECRET="你的公众号AppSecret" + mkdir -p /home/workspace/wxtoken + OUT="/home/workspace/wxtoken/token.json" + resp=$(curl -s "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${SECRET}") + echo "$resp" | grep -q '"access_token"' && echo "$resp" > "$OUT.tmp" && mv "$OUT.tmp" "$OUT" + ``` + 加完点一次「执行」,确认 `/home/workspace/wxtoken/token.json` 生成、内容含 access_token。 + +2. **建站点 + nginx**(宝塔「网站」→ 添加,域名填 IP `162.14.122.200`,纯静态)。 + 站点「配置文件」的 `server { }` 里加(密钥换成 `openssl rand -hex 24` 生成的强随机串): + ```nginx + location = /wxtoken/token.json { + if ($http_authorization != "Bearer <强随机密钥>") { return 403; } + default_type application/json; + alias /home/workspace/wxtoken/token.json; + add_header Cache-Control "no-store"; + } + ``` + 自测:带密钥能取、不带 403。 + +3. **微信 IP 白名单**:只填腾讯云 `162.14.122.200`(换 token 只发生在这台)。 + +### gateway(132 的 .env) ``` -```bash -chmod +x /home/workspace/wxtoken/refresh.sh -/home/workspace/wxtoken/refresh.sh # 手动跑一次,确认 token.json 生成 -cat /home/workspace/wxtoken/token.json # 应有 {"access_token":"...","expires_in":7200} +WECHAT_TOKEN_URL=http://162.14.122.200/wxtoken/token.json +WECHAT_TOKEN_SECRET=<与 nginx 那串一字不差> ``` +配了 gateway 就**只从中控拉、不自己换 token**;不配则维持直连。 -### 2. cron 每 90 分钟刷新(token 有效 2h,留余量) -```bash -crontab -e -# 加:*/90 * * * * /home/workspace/wxtoken/refresh.sh -``` - -### 3. 本地 HTTP 服务只绑 127.0.0.1(不出公网,交给 frp) -```bash -cd /home/workspace/wxtoken && nohup python3 -m http.server 9099 --bind 127.0.0.1 >/dev/null 2>&1 & -# 建议做成 systemd 服务常驻,重启不丢 -``` - -### 4. 腾讯云 `frpc.toml` 追加(暴露本地 9099 给隧道) -> 腾讯云跑着 frps;要把它自己的本地服务经 stcp 暴露,需在该机也跑一个 frpc(或复用已有)。 -```toml -[[proxies]] -name = "wxtoken" -type = "stcp" -secretKey = "换成一串强随机密钥" # 与 132 visitor 一致 -localIP = "127.0.0.1" -localPort = 9099 -``` - -## 二、132 侧(frpc 那台) - -`frpc.toml` 追加一个 visitor,把腾讯云的 token 服务映射到 132 本地 9099: -```toml -[[visitors]] -name = "wxtoken-visitor" -type = "stcp" -serverName = "wxtoken" -secretKey = "换成同一串强随机密钥" # 与腾讯云那串一字不差 -bindAddr = "127.0.0.1" -bindPort = 9099 -``` -reload frpc 后自测(在 132 上): -```bash -curl -s http://127.0.0.1:9099/token.json # 应返回腾讯云那份 token -``` - -## 三、gateway(132 的 .env) -``` -# 容器里的 127.0.0.1 是容器自己,须用 host.docker.internal 指到宿主机 -WECHAT_TOKEN_URL=http://host.docker.internal:9099/token.json -WECHAT_TOKEN_SECRET= -``` -- compose 已加 `extra_hosts: host.docker.internal:host-gateway`(容器可达宿主机端口)。 -- 密钥留空即可:stcp 的 secretKey 已是网络层鉴权,token 不上公网。 -- 配了 gateway 就**只从中控拉、不自己换 token**;不配则维持直连。 - -## 四、切换 + 验证(关键,坐实 qrcode 不受 IP 限制) +## 切换 + 验证(关键,坐实 qrcode 不受 IP 限制) 1. 先让**本地 IP 和腾讯云 IP 都在白名单**,配好上面一切、部署,扫码确认能登录。 -2. 去公众平台把**本地 IP 从白名单删掉,只留腾讯云 `162.14.122.200`**,再扫码: +2. 去公众平台把**本地 IP 删掉、只留腾讯云 `162.14.122.200`**,再扫码: - **能登录** → 换 token 走了中控、建二维码本地也没被拦,方案坐实、IP 隐患彻底消除。 - **建二维码报 40164** → qrcode 也受 IP 限制,改用备选:腾讯云装 tinyproxy 正向代理, gateway 加 `WECHAT_HTTP_PROXY`(需再改一处 http 客户端)。 +## 安全提醒 + +- `WECHAT_TOKEN_SECRET` 用强随机串(`openssl rand -hex 24`),别用品牌名等易猜词—— + 泄露就能拉到 token 冒用公众号 2 小时。 +- token 走公网明文传是本方案的固有代价;要消除,后续可改 frp stcp 隧道(token 不出公网)。 + ## 排查 - gateway 日志 `[wxlogin] 建二维码失败: ... 40164` → 中控没生效或没配 env,仍在本地换 token -- 132 上 `curl 127.0.0.1:9099/token.json` 不通 → frp stcp 没打通(检查两端 secretKey 一致、frpc 都 reload 了) -- token.json 过期/为空 → cron 没跑或 refresh.sh 手动生成失败 -- 容器内拉不到(宿主机能拉到)→ compose 的 extra_hosts 没生效,或用了 127.0.0.1 而非 host.docker.internal +- gateway 拉取 403 → `WECHAT_TOKEN_SECRET` 与 nginx 里的密钥不一致 +- token.json 过期/为空 → 宝塔计划任务没跑,或脚本手动执行失败