# 付费正文构建期加密与密钥鉴权下发
# Why
付费正文当前零防护。实测对 needPay: true 的算法付费页做无 Cookie、无 JS 的裸 curl:HTTP 200、60561 字节,提取出 4793 字符正文,与源 Markdown 全文 4958 字符基本等长,全文可读。
三条根因各自独立成立:
- VuePress 1 是构建期预渲染而非纯客户端渲染,每页
distHTML 内含完整正文。 - Markdown 编译进
assets/js/*.js,无鉴权可直接下载。 theme/components/Page.vue的contentHtmlShow = contentHtml.slice(0, 25000)是从已渲染的完整 DOM 切预览,要能切必须先有全文。现有锁是一层帘子,不是一道门。
theme/util/api.js 现有 40 余个接口全属 user / message / weapp-question,没有任何正文下发接口,正文 100% 来自构建产物。
同时 .vuepress/generated-static-docs/ 下 140 个明文 JSON 既未被 git 跟踪也未被 .gitignore 覆盖,一次 git add -A 即会把全部付费正文明文提交入库。
# What Changes
- 生成期用 AES-256-GCM 加密付费正文 HTML,构建产物只含密文、明文预览段与目录 headers。
- 每篇独立密钥,由主密钥经
HMAC-SHA256(masterSecret, documentId)派生,主密钥不进仓库、不进构建产物。 - 新增
GET /api/interview/doc/key密钥下发接口,服务端校验登录态与会员状态后返回该篇密钥。 - 客户端用 WebCrypto 解密并注入正文,
StaticInterviewDocument从同步渲染改为异步解密渲染。 - 明文预览段改为在生成脚本按节点边界切分,不再由客户端从全文切;预览段进构建产物承担 SEO。
Page.vue的正文就绪检测与骨架屏状态机适配异步解密,覆盖取不到密钥、非会员、WebCrypto 不可用三条降级路径。.gitignore补充明文产物目录;密文 JSON 可入库,明文源 Markdown 不入库。- 保留
v版本字段与PAID_DOC_ENCRYPT开关,支持一键回退到明文形态。
# Scope
本方案是提高门槛,不是访问控制。密钥必然在浏览器内存中出现一次,能打断点的用户仍可取得正文;已付费会员亦可循环调用接口取得全部密钥。目标是挡住 curl、view-source、禁用 JS、批量爬虫与顺手复制,不追求密码学意义上的不可破。真正的访问控制需要正文本身由服务端鉴权下发,不在本次范围。
已知代价:付费页可被搜索引擎索引的正文从全文缩减为预览段,长尾关键词覆盖会下降。这是任何有效防护的必然成本,已接受。
涉及两个仓库:
interview-vuepress-site(本仓):生成脚本、StaticInterviewDocument、Page.vue、api.js、.gitignoreinterview-koa2-backend:新增routes/interviewClient/doc.js、密钥派生工具、conf/secretKeys.js、按用户维度限流
鉴权逻辑本身不需要新写——后端 middlewares/loginChecks.js 的 checkInterviewLogin 已提供 ctx.isVip,routes/interviewClient/video.js 已有会员闸门先例,直接复用。
不修改支付流程、会员价格、登录鉴权逻辑、部署脚本与埋点。不引入新的运行时依赖,不新增第三方统计。