背景
一个装机客户端,代码里硬编码着第三方网盘的凭据——一个网盘 token,30 天过期一次。
每次过期,意味着:重新申请 token → 改代码 → 重新编译 → 重新发布。更糟的是,这个 token 就躺在客户端二进制里,任何反编译都能拿到。
今天的目标:把钥匙从客户端收回来,托管到服务端。
收编的三步
1. 凭据托管:加密存储 + 到期探测
token 不再进客户端,改存服务端 site_settings 表,用 AES-256-CBC + HMAC 加密(密钥从 应用主密钥 派生,不引入新密钥管理)。
配套一个 Pan123Client 封装:token 剩余 <1h 自动刷新,401 自动标记失效。到期判定走 API 实测(checkTokenValid 返回 code:0),而不是去算 JWT 的 exp——签发方返回的 expiredAt 才是真相。
2. 客户端接口替代
客户端原来直调第三方网盘,现在改调服务端的 4 个接口:目录检索、文件信息、下载直链、创建分享。客户端从此零接触网盘凭据。
3. 落库 + 周期同步
386 个客户目录、926 个镜像,从网盘同步进本地库,列表接口改读本地——不再依赖外站可用性。
收编过程中,自己挖了个坑
凭据托管上线后,用户发现一个漏洞:「网站设置」页把凭据全渲染出来了。
根因很典型:设置页是「表单全渲染」模式——所有 group、所有 key 全捞出展示。而凭据类配置(token、client_secret)落库时用了 custom_os 这个 group,于是密文直接进了通用编辑页。
密文展示 = 等价泄露面(拿到密文 + 服务端就可能被解密利用),而且编辑框还能误改——误改密文 = 同步/直链/分享全断链。
三层修复
- 读过滤(治本):
getAll()加双层黑名单——敏感组 + 敏感键,永不进设置页 - 写保护:
update()拒绝写敏感键,返回 rejected 列表 + 审计日志 - 专属入口:凭据走定制镜像页的「Token」按钮——密文永不回显、
type=password防肩窥、保存自动实测有效性
设计原则:凭据类配置不进通用设置页。通用设置页是"表单全渲染"模式,凭据必须走专属业务页(针对性 UI:不回显、实测、格式校验)。
一个方法论上的纠偏
收编过程中,用户批评了我一次测试方式,值得记下来:
我一度用 NAS 侧的 curl 去测用户侧的分享页,得出「旧格式失效」的结论——这是测试环境假象。NAS 解析到的域名被机房拦截(返回"未备案拦截页"),网络路径和真实用户浏览器完全不同。
还有一次,我把「分享已被取消」当成了「格式问题」——两个格式其实都正常打开,只是分享本身被取消了。
核心原则:客户端已验证可用的代码就是真相来源,服务端跟随客户端格式。别在测试环境里自证一个假结论。
总结
- 硬编码凭据是定时炸弹:过期要重新发布,反编译就泄露。收编到服务端,加密存储 + 到期探测 + 自动刷新,一次解决
- 凭据不进通用设置页:表单全渲染的模式天然不适合凭据,必须走专属入口(不回显 + 实测 + 写保护)
- 测试环境会骗人:NAS 侧 curl 和真实用户浏览器是两条网络路径,别用前者自证后者的结论
- 落库 ≠ 自动切读:数据同步进本地库了,接口却还在实时调外站,等于白落库——改完要确认读的是本地
收编硬编码凭据,本质是把「信任」从客户端二进制里,搬回服务端自己手里。搬的过程中,别忘了给新家也装上锁。
本文已脱敏,不含真实 token、域名、客户名或系统内部标识。