diff --git a/docs/security/atu-prod-hardening-playbook.zh-CN.md b/docs/security/atu-prod-hardening-playbook.zh-CN.md deleted file mode 100644 index e7dcdd6..0000000 --- a/docs/security/atu-prod-hardening-playbook.zh-CN.md +++ /dev/null @@ -1,361 +0,0 @@ -# `atu-prod` 分支前端加固迁移执行手册 - -## 目的 - -这份文档用于在后续切换到 `atu-prod` 分支后,按固定步骤复用当前分支已经落地的前端防扒取加固方案。 - -使用方式很简单: - -- 当你切到 `atu-prod` 后,只要说明“按 `atu-prod-hardening-playbook.zh-CN.md` 执行” -- 我就按这份文档中的顺序检查、迁移、修复和验证 - -这份文档不是泛泛的说明,而是面向实际执行的作业手册。 - -## 适用前提 - -- `atu-prod` 分支与当前分支在业务功能上大体一致 -- 主要差异集中在样式、布局、局部结构命名 -- 活动页和榜单页的核心交互、接口、资源使用方式基本相同 - -如果后续发现 `atu-prod` 在页面结构上差异很大,这份手册仍然能作为基线,但执行时要允许局部调整。 - -## 迁移目标 - -切换到 `atu-prod` 后,需要尽量恢复当前分支已经完成的这些能力: - -1. 受保护页面仅允许在 App 环境下访问 -2. 运行时基础防调试、防复制、防拖拽和动态水印 -3. `Activities / Ranking` 图片地址保护与运行时解析 -4. `background-image` 相关真实图链泄露点收口 -5. 构建产物哈希化和可读性降低 -6. 生产相关模式日志清理与保留日志开关 -7. `v-show` 到 `v-if` 的主内容延迟挂载改造 -8. `BackgroundLayer` 的 `useCanvas` 背景合成方案 -9. 明显语义类名收口 -10. 已发现问题页的修复和清理 - -## 执行顺序 - -后续在 `atu-prod` 分支执行时,按下面顺序做,不要跳步。 - -### 第 1 步:先对照基础能力文件 - -优先检查这些全局能力文件是否已经存在、是否需要同步: - -- [src/utils/routeGuard.js](/D:/programs/likei-h5/src/utils/routeGuard.js) -- [src/utils/http.js](/D:/programs/likei-h5/src/utils/http.js) -- [src/config/security.js](/D:/programs/likei-h5/src/config/security.js) -- [src/utils/runtimeSecurity.js](/D:/programs/likei-h5/src/utils/runtimeSecurity.js) -- [src/main.js](/D:/programs/likei-h5/src/main.js) -- [src/App.vue](/D:/programs/likei-h5/src/App.vue) -- [src/config/imagePaths.js](/D:/programs/likei-h5/src/config/imagePaths.js) -- [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) -- [src/utils/protectedAssetRuntime.js](/D:/programs/likei-h5/src/utils/protectedAssetRuntime.js) -- [src/directives/smartImage.js](/D:/programs/likei-h5/src/directives/smartImage.js) -- [src/utils/image/imageCacheManager.js](/D:/programs/likei-h5/src/utils/image/imageCacheManager.js) -- [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) -- [vite.config.js](/D:/programs/likei-h5/vite.config.js) -- [package.json](/D:/programs/likei-h5/package.json) -- [scripts/build-with-version.js](/D:/programs/likei-h5/scripts/build-with-version.js) - -这一步的目标是先把“全局基础设施”对齐,再进页面级迁移。 - -### 第 2 步:扫描 `Activities / Ranking` 页面 - -重点扫描这两个目录: - -- [src/views/Activities](/D:/programs/likei-h5/src/views/Activities) -- [src/views/Ranking](/D:/programs/likei-h5/src/views/Ranking) - -执行时重点看四类点: - -1. 是否仍有大量直接 `` -2. 是否仍有 `background-image: url(...)` -3. 是否存在 `v-show="!isLoading"` 控制主内容 -4. 是否存在明显语义类名、明显业务含义 chunk 入口、测试日志 - -### 第 3 步:优先迁移高风险页面 - -后续在 `atu-prod` 中优先处理这些已经确认过风险较高、且当前分支已加固的页面: - -- [src/views/Activities/LesserBairam/index.vue](/D:/programs/likei-h5/src/views/Activities/LesserBairam/index.vue) -- [src/views/Activities/LoginReward/index.vue](/D:/programs/likei-h5/src/views/Activities/LoginReward/index.vue) -- [src/views/Activities/LuckyDollars/Season3/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season3/index.vue) -- [src/views/Activities/LuckyDollars/Season4/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season4/index.vue) -- [src/views/Activities/SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue) -- [src/views/Activities/SpringFestival/Ranking.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Ranking.vue) -- [src/views/Activities/SpringFestival/Task.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Task.vue) -- [src/views/Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue) -- [src/views/Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue) -- [src/views/Ranking/GamesKing/index.vue](/D:/programs/likei-h5/src/views/Ranking/GamesKing/index.vue) -- [src/views/Ranking/KingAndQueen/TopList.vue](/D:/programs/likei-h5/src/views/Ranking/KingAndQueen/TopList.vue) -- [src/views/Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue) -- [src/views/Ranking/WeeklyStar/WeeklyStar.vue](/D:/programs/likei-h5/src/views/Ranking/WeeklyStar/WeeklyStar.vue) - -## 页面级迁移检查清单 - -后续每处理一个页面,都按这份清单检查。 - -### A. 图片链路 - -- 是否统一通过 `getPngUrl()` / `getWebpUrl()` 走受保护地址 -- 是否尽量使用 `v-smart-img` -- 是否存在直接暴露真实 OSS 地址的模板位点 -- 是否存在必须保留的 CSS 背景图场景 - -### B. 背景层 - -- 页面背景是否可改成 [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) -- 如果是多层纯装饰背景,是否开启 `:useCanvas="true"` -- 如果只是普通内容图,不强制改成 canvas - -说明: - -- `useCanvas` 不应一刀切设成默认值 `true` -- 它只针对敏感背景层启用,避免把普通页面也全部切到更重的渲染路径 - -### C. 主内容挂载时机 - -- 页面是否仍使用 `v-show="!isLoading"` 控制主内容 -- 如果页面存在预加载和监听时序问题,优先改成 `v-if="!isLoading"` -- 如果页面依赖 `IntersectionObserver`,应在 `await nextTick()` 后再注册 -- 如果预加载可能失败,确保在 `finally` 中放行主内容,避免整页空白 - -### D. 语义暴露 - -- 是否有过于直白的局部类名 -- 是否有业务含义明显的本地变量名或测试痕迹 -- 是否有无用日志、调试文案、历史残留注释 - -### E. 组件生命周期 - -- `IntersectionObserver` 是否在卸载时 `disconnect()` -- 定时器是否在卸载时清理 -- 残留监听代码是否已经失效却未移除 - -## 已知问题与处理记录 - -后续迁到 `atu-prod` 时,优先留意这些已经踩过的坑。 - -### 1. `v-show` 会让主内容 DOM 提前暴露 - -现象: - -- 页面还在预加载时,DOM 已经被挂载 -- 容易被直接从 `Elements` 面板里抄结构 -- 某些触底监听也会因为节点时序问题失灵 - -处理方式: - -- 将主内容改为 `v-if="!isLoading"` -- 对依赖监听的组件,在 `nextTick()` 后注册观察器 -- 在 `finally` 中放行主内容 - -涉及页面: - -- `LoginReward` -- `LuckyDollars/Season4` -- `SpringFestival` -- `Ranking/Couple` -- `Ranking/Overall` - -### 2. `IntersectionObserver` 挂载时机不稳 - -现象: - -- 监听节点存在,但绑定太早 -- 页面结构还没稳定时就执行 `observe()` -- 页面切走后观察器残留 - -处理方式: - -- `await nextTick()` -- 再 `observer.observe(...)` -- `onUnmounted` 时 `disconnect()` - -重点页面: - -- [src/views/Activities/SpringFestival/Ranking.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Ranking.vue) - -### 3. `Ranking/Couple/Ranking.vue` 曾有残留无效加载监听代码 - -现象: - -- 组件里保留了已经取消的加载模块逻辑 -- 相关 `ref` 节点已不存在,但监听代码还在 - -处理方式: - -- 清理失效的 `IntersectionObserver` 逻辑 -- 删除无效加载模块相关变量 -- 保留当前真实在用的数据展示逻辑 - -### 4. `Ranking/Couple/Ranking.vue` 曾出现乱码注释 - -现象: - -- 早期编辑和编码处理后,中文注释出现乱码 - -处理方式: - -- 清理乱码注释 -- 统一改为中文注释 -- 保持逻辑不变,只修复可读性 - -### 5. `BackgroundLayer` 为什么不是默认 `useCanvas=true` - -结论: - -- 因为它是公共组件 -- 默认全开会把所有背景层都切到 canvas 路径 -- 会增加普通页面的图片加载、重绘和兼容风险 - -当前策略: - -- 只在高风险的 `Activities / Ranking` 页面显式传入 `:useCanvas="true"` - -### 6. `toCssBackgroundImage()` 的作用 - -结论: - -- 它不是加密层 -- 它是把 `likei-protected:` 地址还原成 CSS 可用的 `url(...)` -- 主要用于必须保留 `background-image` 的点位 - -相关文件: - -- [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) - -## 构建与验证步骤 - -### 基础构建 - -执行: - -```bash -npm run build -``` - -预期: - -- 构建成功 -- 保留已知提示,但不新增本轮改动引起的报错 - -### 生产态验证 - -执行: - -```bash -npm run dev:prod -``` - -或: - -```bash -npm run build -npm run preview -``` - -验证点: - -- 非 App 环境访问受保护页会被拦截 -- 公共页可正常打开 -- `Activities / Ranking` 页面的图片仍能显示 -- `BackgroundLayer` 的 canvas 背景仍能正常渲染 -- 触底加载和倒计时逻辑不受影响 - -### 日志构建验证 - -执行: - -```bash -npm run build:prod -npm run build:prod:logs -npm run build:atu-prod -npm run build:atu-prod:logs -``` - -验证点: - -- 默认生产相关包清理 `console.log`、`console.debug`、`console.info`、`debugger` -- 带 `:logs` 的命令仍保留调试日志,便于 App 内定位问题 -- Jenkins 使用原有命令不需要改发布流程 - -## 建议的迁移方法 - -后续切到 `atu-prod` 后,建议按下面方式推进: - -1. 先同步全局基础能力文件 -2. 再扫描 `Activities / Ranking` -3. 先迁移高风险页面 -4. 每迁移一页就做局部验证 -5. 最后统一跑 `npm run build` -6. 再补文档同步 - -## 后续文档同步方法 - -如果后续主分支继续更新了这些安全文档,想把最新 md 同步到当前分支,不需要再手工复制。 - -先决条件: - -- 主分支上的文档更新已经提交到 Git -- 当前已经切换到目标分支,例如 `atu_prod` - -执行命令: - -```bash -npm run docs:sync:security -- master -``` - -如果你想从远端主分支同步,也可以用: - -```bash -npm run docs:sync:security -- origin/master -``` - -这条命令会同步这些文件: - -- `docs/security/frontend-hardening.md` -- `docs/security/frontend-hardening.zh-CN.md` -- `docs/security/frontend-hardening.zh-CN.updates.md` -- `docs/security/atu-prod-hardening-playbook.zh-CN.md` - -对应脚本文件: - -- [scripts/sync-security-docs.js](/D:/programs/likei-h5/scripts/sync-security-docs.js) - -注意: - -- 如果源分支里还没有提交这些 md,脚本会直接报错 -- 所以推荐流程是“先在主分支提交文档,再切到目标分支执行同步命令” - -## 文档同步要求 - -后续在 `atu-prod` 做迁移时,文档也要同步更新,至少包含: - -- 本次迁移到 `atu-prod` 的实际范围 -- 哪些页面已迁移 -- 哪些页面因结构差异暂未迁移 -- 新遇到的问题和处理方式 -- 最终验证结果 - -建议优先更新这些文档: - -- [docs/security/frontend-hardening.zh-CN.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.md) -- [docs/security/frontend-hardening.zh-CN.updates.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.updates.md) -- [docs/security/atu-prod-hardening-playbook.zh-CN.md](/D:/programs/likei-h5/docs/security/atu-prod-hardening-playbook.zh-CN.md) - -## 后续对我下指令的推荐说法 - -后续你切到 `atu-prod` 分支后,可以直接这样说: - -- 按 `atu-prod-hardening-playbook.zh-CN.md` 执行一遍 -- 先按手册同步全局加固能力,再扫描 `Activities / Ranking` -- 按手册把高风险页面优先迁移,并同步更新文档 - -看到这类指令后,我默认会: - -- 先核对分支当前状态 -- 再按这份文档逐项执行 -- 遇到结构差异时在不偏离目标的前提下做适配 -- 最后把实际执行结果写回文档 diff --git a/docs/security/frontend-hardening.md b/docs/security/frontend-hardening.md index 45a27da..b2aa852 100644 --- a/docs/security/frontend-hardening.md +++ b/docs/security/frontend-hardening.md @@ -2,20 +2,20 @@ ## Purpose -This document records the frontend-only hardening work added to this repository to raise the cost of scraping, asset reuse, and low-effort page reconstruction. +This document records the frontend-only hardening currently applied on the main branch to raise the cost of scraping, asset reuse, and low-effort page reconstruction. The goal is to make abuse less convenient, not to claim absolute protection. ## Scope -All changes described here are limited to the frontend project. +All changes described here stay within the frontend project. -This work does not rely on: +They do not rely on: - backend API changes - private OSS buckets - signed image URLs -- server-side watermark rendering +- server-side image rewriting ## What Was Added @@ -37,7 +37,7 @@ Behavior: ### 2. Runtime anti-debug and anti-copy restrictions -Protected pages now add a lightweight runtime restriction layer in production-like modes. +Protected pages add a lightweight runtime restriction layer in production-like modes. Main files: @@ -50,7 +50,6 @@ Behavior: - blocks common devtools shortcuts - blocks right-click, drag, copy, and text selection on protected pages - reduces long-press image save convenience -- adds page-level dynamic watermark overlays ### 3. Protected asset URL layer for `Activities` and `Ranking` @@ -69,7 +68,7 @@ Behavior: - `getPngUrl()` and `getWebpUrl()` emit `likei-protected:...` - runtime code resolves protected URLs only when rendering is needed - most image rendering flows now end up using cached blob/object URLs -- even pages that missed `v-smart-img` still get a runtime fallback for protected image `src` +- pages that missed `v-smart-img` still get a runtime fallback for protected image `src` ### 4. `background-image` compatibility fixes @@ -102,11 +101,9 @@ Behavior: - CSS files also use hash-based names - static asset filenames are hash-based -This does not stop reverse engineering, but it lowers the convenience of reading page structure from `dist/`. - ### 6. Production log stripping with a troubleshooting switch -Production-like builds strip common debug output, while still supporting a keep-logs build for app-side troubleshooting. +Production-like builds strip common debug output while still supporting a keep-logs build for app-side troubleshooting. Main files: @@ -116,9 +113,9 @@ Main files: Behavior: -- `production` and `production-atu` builds remove `console.log` -- `production` and `production-atu` builds remove `console.debug` -- `production` and `production-atu` builds remove `console.info` +- `production` builds remove `console.log` +- `production` builds remove `console.debug` +- `production` builds remove `console.info` - production-like builds remove `debugger` - `console.warn` and `console.error` are preserved - Jenkins can keep using the original default build commands @@ -128,8 +125,6 @@ Useful commands: ```bash npm run build:prod npm run build:prod:logs -npm run build:atu-prod -npm run build:atu-prod:logs ``` ### 7. Deferred main DOM mounting for selected high-risk pages @@ -148,15 +143,10 @@ Extra handling was added where needed: - pages with `IntersectionObserver` targets now register observers only after `await nextTick()` - preload completion still clears `isLoading` in `finally`, so preload failures do not leave the page permanently blank -- this keeps the original lazy-load behavior more stable after switching from `v-show` to `v-if` - -Important limit: - -This is only a weak hardening step. It reduces early DOM exposure and makes initial inspection less direct, but it does not stop scraping once the page is fully rendered on the client. ### 8. Canvas-rendered decorative background stacks -The shared background layer component now supports a canvas rendering mode for stacked background images. +The shared background layer component supports a canvas rendering mode for stacked background images. Main file: @@ -181,11 +171,9 @@ Behavior: - most page-level decorative background structure becomes less obvious in Elements inspection - protected asset resolution still goes through the existing runtime/cache pipeline -This does not hide the pixels from a determined attacker, but it reduces direct DOM readability and low-effort structure copying. - ### 9. Stronger production minification and selector cleanup -Production-like builds now use stricter `esbuild` minification flags, and a small set of highly readable page-local class names was reduced on sensitive pages. +Production-like builds use stricter `esbuild` minification flags, and a small set of highly readable page-local class names was reduced on sensitive pages. Main files: @@ -197,13 +185,19 @@ Main files: - [src/views/Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue) - [src/views/Ranking/WeeklyStar/WeeklyStar.vue](/D:/programs/likei-h5/src/views/Ranking/WeeklyStar/WeeklyStar.vue) +### 10. `Ranking/Couple` comment cleanup and helper clarification + +Recent cleanup also covered: + +- [src/views/Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue) +- [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) +- [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) + Behavior: -- production-like builds explicitly minify identifiers, syntax, and whitespace -- legal comments are removed from build output -- some obvious local selector names were replaced with shorter neutral names -- selector cleanup now also covers obvious ranking navigation and history labels on additional `Activities` and `Ranking` pages -- `SpringFestival/Ranking` now waits for `nextTick()` before observing its load sentinel and disconnects the observer on unmount +- garbled comments in `Ranking/Couple/Ranking.vue` were cleaned up and unified into Chinese comments +- `useCanvas` remains an explicit opt-in instead of becoming a global default +- `toCssBackgroundImage()` stays as the CSS bridge for protected asset URLs ## What This Helps Against @@ -226,11 +220,7 @@ These changes still do not fully prevent: - rebuilding the same layout after visual inspection - extracting DOM, CSS, and JS from any client that is allowed to render the page -Important rule: - -If the client can render the page, the client must receive enough information to reproduce the page. - -That is why frontend-only hardening can only increase cost, not provide absolute protection. +If the client can render the page, the client must receive enough information to reproduce it. Frontend-only hardening can only increase cost, not provide absolute protection. ## Recommended Testing @@ -249,11 +239,6 @@ npm run build npm run preview ``` -Expected: - -- protected routes redirect to `/#/not_app` outside the app -- public routes still open normally - ### Asset and page rendering checks In production-like mode, verify: @@ -279,17 +264,3 @@ Expected: - no obvious page names appear in bundle filenames - production bundles no longer retain `console.log`, `console.debug`, `console.info`, or `debugger` -## Suggested Next Steps - -If staying frontend-only: - -- move more decorative shells from plain DOM/CSS into canvas rendering -- reduce semantic structure in the most sensitive event pages -- keep replacing remaining raw `background-image` and direct image flows with protected helpers - -If minimal backend support becomes acceptable later: - -- move sensitive OSS paths to private read -- issue short-lived signed URLs -- watermark sensitive images per user on the server side -- validate app session or device identity before returning sensitive assets diff --git a/docs/security/frontend-hardening.zh-CN.md b/docs/security/frontend-hardening.zh-CN.md index ddd6f0f..22724f9 100644 --- a/docs/security/frontend-hardening.zh-CN.md +++ b/docs/security/frontend-hardening.zh-CN.md @@ -2,24 +2,18 @@ ## 目的 -这份文档用于记录当前仓库内已经落地的前端侧防扒取、防复用加固方案。 +这份文档用于记录当前主分支已经落地的前端防扒取加固方案。 核心目标是提高扒图、抄页面、低成本复刻的门槛,而不是宣称“前端已经绝对防住”。 -如需在后续切换到 `atu-prod` 分支后复用整套处理,请直接参考: - -- [docs/security/atu-prod-hardening-playbook.zh-CN.md](/D:/programs/likei-h5/docs/security/atu-prod-hardening-playbook.zh-CN.md) - ## 范围 -本次加固严格限制在当前前端项目内完成。 - -当前方案不依赖: +本次加固严格限制在当前前端项目内完成,不依赖: - 后端接口改造 -- OSS 私有读 +- 私有 OSS - 短时签名 URL -- 服务端动态水印 +- 服务端动态出图 ## 已落地能力 @@ -39,7 +33,7 @@ - 受保护页面在非 App 环境下会跳转到 `/#/not_app` - 受保护页面内的接口请求在缺少 App bridge 时会被拒绝 -### 2. 运行时防调试、防复制与页面水印 +### 2. 运行时防调试与防复制限制 对受保护页面增加了一层轻量运行时限制。 @@ -54,7 +48,6 @@ - 屏蔽常见开发者工具快捷键 - 屏蔽右键、拖拽、复制、文本选择等交互 - 降低长按保存图片的便利性 -- 给受保护页面增加动态水印覆盖 ### 3. `Activities` / `Ranking` 图片受保护地址层 @@ -77,7 +70,7 @@ ### 4. `background-image` 泄露点补丁 -部分重点页面之前仍通过内联 `background-image: url(...)` 直接暴露真实地址,这类点位已经做了兼容收口。 +部分重点页面之前通过内联 `background-image: url(...)` 直接暴露真实地址,这类点位已经做了兼容收口。 已调整文件: @@ -106,8 +99,6 @@ - CSS 文件名也改为哈希命名 - 静态资源文件名同样使用哈希 -这一步不能阻止逆向,但能降低别人直接从 `dist/` 推断页面结构的便利性。 - ### 6. 生产包自动清理日志,并保留可切换开关 生产相关打包模式下,会自动清掉常见调试日志;同时保留一个方便排查 App 内问题的“保留日志”构建开关。 @@ -120,9 +111,9 @@ 表现: -- `production` 和 `production-atu` 下自动移除 `console.log` -- `production` 和 `production-atu` 下自动移除 `console.debug` -- `production` 和 `production-atu` 下自动移除 `console.info` +- `production` 下自动移除 `console.log` +- `production` 下自动移除 `console.debug` +- `production` 下自动移除 `console.info` - 生产相关模式下自动移除 `debugger` - `console.warn` 和 `console.error` 保留,方便线上排查 - Jenkins 仍可继续使用原有默认打包命令 @@ -132,8 +123,6 @@ ```bash npm run build:prod npm run build:prod:logs -npm run build:atu-prod -npm run build:atu-prod:logs ``` ### 7. 部分高风险页面改为延迟挂载主内容 DOM @@ -153,17 +142,9 @@ npm run build:atu-prod:logs - 对依赖 `IntersectionObserver` 的页面,在 `await nextTick()` 之后再注册观察器 - 预加载结束逻辑仍然放在 `finally` 中,避免预加载失败时页面长期空白 -这一步的作用要明确: - -- 可以减少初始阶段 DOM 直接暴露的程度 -- 可以让“页面刚进来就看 Elements 面板抄结构”变得没那么直接 -- 不能阻止页面渲染完成后的继续抓取 - -也就是说,它属于弱加固,而不是决定性的防扒手段。 - ### 8. 装饰背景堆图改为可选 canvas 渲染 -公共背景组件现在支持可选的 canvas 渲染模式,适合拿来处理活动页和榜单页那种“纯装饰背景层层堆图”的场景。 +公共背景组件现在支持可选的 canvas 渲染模式,适合处理活动页和榜单页那种“纯装饰背景层层堆图”的场景。 主要文件: @@ -184,12 +165,10 @@ npm run build:atu-prod:logs 表现: -- 多张纯装饰背景图会先合成到单个 canvas 中,而不是在 DOM 里直接挂一串 `` +- 多张纯装饰背景图会先合成到单个 canvas 中,而不是在 DOM 里直接挂一个个 `` - 从 Elements 面板直接看页面背景结构会比之前更不直观 - 受保护图片地址仍然继续走现有的运行时解析和缓存链路 -这一步不能阻止有经验的人继续拿到像素内容,但能明显降低“直接看 DOM 抄背景结构”的便利度。 - ### 9. 生产构建混淆再收紧,并补一轮明显语义类名收口 生产相关构建模式现在显式开启更严格的 `esbuild` 压缩参数,同时针对重点页面收了一轮最显眼的本地类名。 @@ -204,35 +183,17 @@ npm run build:atu-prod:logs - [src/views/Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue) - [src/views/Ranking/WeeklyStar/WeeklyStar.vue](/D:/programs/likei-h5/src/views/Ranking/WeeklyStar/WeeklyStar.vue) -表现: - -- 生产相关构建会显式压缩标识符、语法和空白 -- 构建产物中的合法注释会被去掉 -- 一部分过于直白的页面本地类名已经换成更短、更弱语义的名字 - ### 10. `Ranking/Couple` 注释修复与背景组件说明补充 近期对 [src/views/Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue) 做了一次编码与注释清理: -- 清除了上一轮编辑中遗留的乱码注释 -- 将核心业务注释统一收口为中文,便于后续维护和分支迁移 -- 保留当前逻辑不变,只修正文案可读性与排查体验 +- 清除了上一次编辑中遗留的乱码注释 +- 将核心业务注释统一收口为中文,便于后续维护 同时补充说明两个容易混淆的背景层工具点: - [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 的 `useCanvas` 是显式开关,不默认全局开启 -- 原因是 `BackgroundLayer` 是公共组件,默认全开会把所有背景层都切到 canvas 路径,带来额外的图片加载、重绘和兼容风险 -- 当前只在 `Activities / Ranking` 这类高风险页面显式传入 `:useCanvas=\"true\"`,把成本控制在需要加固的范围内 - [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) 中的 `toCssBackgroundImage()` 用于把 `likei-protected:` 地址还原成 CSS 可用的 `background-image: url(...)` -- 这个方法是兼容桥接,不是额外的加密层,主要服务于仍需使用内联背景图的场景 - -如果后续需要在 `atu-prod` 分支做同类处理,建议优先对照这几个文件迁移: - -- [src/views/Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue) -- [src/components/BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) -- [src/utils/protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) -- [docs/security/frontend-hardening.zh-CN.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.md) -- [docs/security/frontend-hardening.zh-CN.updates.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.updates.md) ## 这套方案能挡住什么 @@ -247,20 +208,16 @@ npm run build:atu-prod:logs ## 当前边界 -这套改动仍然无法彻底防住以下情况: +这套改动依然无法彻底防住以下情况: - 通过 Network 面板或抓包获取图片请求 - 有经验的人持续分析前端运行时代码 - 自定义 WebView、自动化脚本、Hook 等方式截获资源 - 仅凭视觉观察后重新临摹布局和样式 -- 从一个本来就被允许渲染页面的客户端中提取 DOM、CSS、JS - -一个关键原则是: +- 从本来就被允许渲染页面的客户端中提取 DOM、CSS、JS 只要客户端能把页面渲染出来,客户端就一定拿到了足够还原页面的信息。 -所以前端侧能做的,本质上只有“提高成本”,而不是“绝对防住”。 - ## 推荐测试方式 ### 受保护页面拦截 @@ -308,17 +265,3 @@ npm run build - 构建产物文件名中不再直观出现页面名 - 生产包中不再保留 `console.log`、`console.debug`、`console.info` 和 `debugger` -## 后续仍可继续推进的方向 - -如果继续坚持只改前端项目,可以优先考虑: - -- 把更敏感的装饰层、外框层继续往 `canvas` 渲染收口 -- 继续减少关键活动页中可直接阅读的结构语义 -- 继续替换剩余直接 `background-image` 或直出图片链路 - -如果后续允许少量后端配合,优先级更高的是: - -- 敏感 OSS 目录改成私有读 -- 使用短时签名 URL -- 给敏感图片做按用户生成的动态水印 -- 资源下发前校验 App 会话或设备身份 diff --git a/docs/security/frontend-hardening.zh-CN.updates.md b/docs/security/frontend-hardening.zh-CN.updates.md index a99fd9c..0486c9c 100644 --- a/docs/security/frontend-hardening.zh-CN.updates.md +++ b/docs/security/frontend-hardening.zh-CN.updates.md @@ -7,29 +7,28 @@ - 生产构建混淆进一步收紧,`esbuild` 在生产相关模式下显式开启了更强的标识符、语法和空白压缩,并去除了合法注释输出。相关文件在 [vite.config.js](/D:/programs/likei-h5/vite.config.js)。 - 公共背景组件 [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 新增了 `useCanvas` 模式,装饰性多层背景会优先绘制到单个 canvas,而不是在 DOM 中直接暴露多张 ``。 - `canvas` 背景已扩展到更多高风险页面: - [LesserBairam/index.vue](/D:/programs/likei-h5/src/views/Activities/LesserBairam/index.vue), - [LoginReward/index.vue](/D:/programs/likei-h5/src/views/Activities/LoginReward/index.vue), - [LuckyDollars/Season3/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season3/index.vue), - [LuckyDollars/Season4/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season4/index.vue), - [SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue), - [Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue), - [Ranking/GamesKing/index.vue](/D:/programs/likei-h5/src/views/Ranking/GamesKing/index.vue), - [Ranking/KingAndQueen/TopList.vue](/D:/programs/likei-h5/src/views/Ranking/KingAndQueen/TopList.vue), - [Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue), + [LesserBairam/index.vue](/D:/programs/likei-h5/src/views/Activities/LesserBairam/index.vue)、 + [LoginReward/index.vue](/D:/programs/likei-h5/src/views/Activities/LoginReward/index.vue)、 + [LuckyDollars/Season3/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season3/index.vue)、 + [LuckyDollars/Season4/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season4/index.vue)、 + [SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue)、 + [Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue)、 + [Ranking/GamesKing/index.vue](/D:/programs/likei-h5/src/views/Ranking/GamesKing/index.vue)、 + [Ranking/KingAndQueen/TopList.vue](/D:/programs/likei-h5/src/views/Ranking/KingAndQueen/TopList.vue)、 + [Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue)、 [Ranking/WeeklyStar/WeeklyStar.vue](/D:/programs/likei-h5/src/views/Ranking/WeeklyStar/WeeklyStar.vue)。 -- 第二轮语义类名收口已继续推进,当前已覆盖: - [Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue), - [LuckyDollars/Season4/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season4/index.vue), - [SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue), - [SpringFestival/Ranking.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Ranking.vue), - [Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue), +- 第二轮语义类名收口已经继续推进,当前已覆盖: + [Ranking/Couple/index.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/index.vue)、 + [LuckyDollars/Season4/index.vue](/D:/programs/likei-h5/src/views/Activities/LuckyDollars/Season4/index.vue)、 + [SpringFestival/index.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/index.vue)、 + [SpringFestival/Ranking.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Ranking.vue)、 + [Ranking/Overall/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Overall/Ranking.vue)、 [Ranking/WeeklyStar/WeeklyStar.vue](/D:/programs/likei-h5/src/views/Ranking/WeeklyStar/WeeklyStar.vue)。 - [SpringFestival/Ranking.vue](/D:/programs/likei-h5/src/views/Activities/SpringFestival/Ranking.vue) 已额外做了时序加固: - 组件会在 `nextTick()` 之后再注册 `IntersectionObserver`,并在 `onUnmounted` 时执行 `disconnect()`,避免监听节点还没稳定挂载时就注册,或页面切换后残留观察器。 -- [Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue) 已清理上一轮遗留的乱码注释,并统一替换为中文注释,方便后续排查与跨分支迁移。 -- [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 的 `useCanvas` 继续保持显式开启,而没有改成默认值 `true`: - 这是为了避免把所有背景层都切到 canvas 渲染路径,给普通页面增加不必要的加载、绘制和重绘成本。 -- [protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) 中的 `toCssBackgroundImage()` 作用也已明确: + 组件会在 `nextTick()` 之后再注册 `IntersectionObserver`,并在 `onUnmounted` 时执行 `disconnect()`。 +- [Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue) 已清理上一轮遗留的乱码注释,并统一替换为中文注释。 +- [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue) 的 `useCanvas` 继续保持显式开启,而没有改成默认值 `true`。 +- [protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js) 中的 `toCssBackgroundImage()` 作用已明确: 它负责把 `likei-protected:` 地址还原为 CSS 可直接使用的 `url(...)` 字符串,主要用于必须保留 `background-image` 的内联样式场景。 ## 当前边界 @@ -42,17 +41,4 @@ - 使用 `npm run build` 验证生产包可正常构建。 - 使用 `npm run dev:prod` 或 `npm run preview` 验证活动页、榜单页在生产态逻辑下仍能正常显示。 -- 重点检查 `SpringFestival`、`Season4`、`WeeklyStar`、`Overall` 这几类近期改动较多的页面。 - -## `atu-prod` 迁移建议 - -- 如果后续要在 `atu-prod` 分支复用同类处理,优先迁移本轮涉及的三个核心文件: - [Ranking/Couple/Ranking.vue](/D:/programs/likei-h5/src/views/Ranking/Couple/Ranking.vue)、 - [BackgroundLayer.vue](/D:/programs/likei-h5/src/components/BackgroundLayer.vue)、 - [protectedAssets.js](/D:/programs/likei-h5/src/utils/protectedAssets.js)。 -- 同步迁移文档文件,避免分支间的实现与说明脱节: - [frontend-hardening.zh-CN.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.md)、 - [frontend-hardening.zh-CN.updates.md](/D:/programs/likei-h5/docs/security/frontend-hardening.zh-CN.updates.md)。 -- 后续如果主分支继续更新了这些文档,切到目标分支后可直接执行: - `npm run docs:sync:security -- master` -- 该命令依赖脚本 [scripts/sync-security-docs.js](/D:/programs/likei-h5/scripts/sync-security-docs.js),前提是主分支里的文档版本已经提交到 Git。 +- 重点检查 `SpringFestival`、`Season4`、`WeeklyStar`、`Overall` 这些近期改动较多的页面。 diff --git a/src/App.vue b/src/App.vue index e99f3ab..5069c58 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,47 +1,24 @@ @@ -82,31 +59,4 @@ body { -webkit-touch-callout: none; user-select: none; } - -.security-watermark { - position: fixed; - inset: 0; - z-index: 9999; - pointer-events: none; - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 48px 24px; - padding: 40px 16px; - opacity: 0.11; - overflow: hidden; -} - -.security-watermark span { - display: flex; - align-items: center; - justify-content: center; - min-height: 96px; - color: #0f172a; - font-size: 14px; - font-weight: 700; - letter-spacing: 1px; - text-transform: uppercase; - transform: rotate(-24deg); - white-space: nowrap; -}