🛡️ 前端水印方案全解析——从「能被 F12 删掉」到「删不掉」的反破解实战

冷门知识点 | 安全防护 | 领导要求加个水印保护公司敏感信息,你两分钟写完 position: fixed + opacity: 0.1,结果运营同事打开 DevTools 一键删除......今天就把水印做到「删不掉」。


问题场景

运营团队正在使用一个内部运营后台,页面上展示着千万级 GMV 数据。安全部门要求所有数据页面必须带上员工工号水印。

第一天:PM:「加个水印」 你:用 position: fixeddiv 覆盖全屏,加 pointer-events: none,搞定。

第二天:运营反馈:「我用 F12 把水印那个 div 删掉了,截图发给老板了......没问题吧?」 你:「......」

内核中 CSS-only 水印在 DevTools 面前就是纸糊的。看似「有」实则「无」,用户只需随手删一个 DOM 节点就破了。


原因分析

前端水印常见的「纸上谈兵」实现方式:

html 复制代码
<!-- ❌ 最脆弱的水印 -->
<div id="watermark" style="position:fixed;inset:0;pointer-events:none;opacity:0.08;z-index:99999;">
  <span>机密-张三-2026-07-23</span>
</div>

三大致命伤:

  1. DOM 可删除 --- DevTools 选中节点按 Delete 就没了
  2. DOM 可隐藏 --- display: nonevisibility: hidden 一键隐身
  3. CSS 可覆盖 --- 修改 z-indexopacity 直接失效

更「高级」一点的多层水印 + 脚本定时重绘,也只能防住普通用户。只要懂一点 JS debugging(断点除定时器、删除 DOM),一样破防。


解决方案

方案一:Canvas 全屏覆盖(初级加固)

用 Canvas 绘制水印渲染为背景图片,通过 background-repeat 铺满,DOM 路径上无从删除。

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <style>
    .watermarked {
      /* 背景图将在 JS 中设置 */
      background-repeat: repeat;
      background-size: 200px 150px;
    }
  </style>
</head>
<body id="app">
  <div>敏感数据区域</div>
  <script>
    function createWatermark(text = '机密-张三-2026-07-23') {
      const canvas = document.createElement('canvas');
      canvas.width = 200;
      canvas.height = 150;
      const ctx = canvas.getContext('2d');
      
      ctx.rotate(-20 * Math.PI / 180);  // 旋转 -20 度
      ctx.font = '14px Microsoft YaHei';
      ctx.fillStyle = 'rgba(0, 0, 0, 0.08)';
      ctx.fillText(text, 20, 80);
      
      document.getElementById('app').style.backgroundImage =
        `url(${canvas.toDataURL('image/png')})`;
    }
    createWatermark();
  </script>
</body>
</html>

优点: DOM 树里找不到水印元素,无法「Delete」删除 缺点: 修改 background-image CSS 属性即可覆盖,或截图后简单 PS 擦除


方案二:Canvas + MutationObserver 双重监听(中级防护)

在方案一基础上,加入 MutationObserver 监听 DOM/样式篡改,一旦发现水印被篡改立即重建。

javascript 复制代码
function setupWatermark(text) {
  const target = document.getElementById('app');
  let watermarkStyleId = 'wm-style';

  function applyWatermark() {
    const canvas = document.createElement('canvas');
    canvas.width = 200;
    canvas.height = 150;
    const ctx = canvas.getContext('2d');
    ctx.rotate(-20 * Math.PI / 180);
    ctx.font = '14px Microsoft YaHei';
    ctx.fillStyle = 'rgba(0, 0, 0, 0.08)';
    ctx.fillText(text, 20, 80);
    
    target.style.backgroundImage = `url(${canvas.toDataURL()})`;
  }

  // 第一次应用
  applyWatermark();

  // MutationObserver 监听 background-image 是否被修改
  const observer = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
      if (mutation.type === 'attributes' &&
          mutation.attributeName === 'style') {
        const bg = target.style.backgroundImage;
        if (!bg || bg === 'none') {
          applyWatermark(); // 被删了?马上恢复!
        }
      }
    }
  });

  observer.observe(target, {
    attributes: true,
    attributeFilter: ['style'],
  });

  // 额外防御:定时检查(防 MutationObserver 被断开)
  const interval = setInterval(() => {
    const bg = target.style.backgroundImage;
    if (!bg || bg === 'none') {
      applyWatermark();
    }
  }, 1000);

  // 对外暴露清理函数
  return () => {
    observer.disconnect();
    clearInterval(interval);
  };
}

const cleanup = setupWatermark('机密-张三-2026-07-23');

增加了三层防御: 1️⃣ MutationObserver 监听 style 属性变更 → 篡改后即时恢复 2️⃣ 定时轮询兜底 → 防 Observer 被断开 3️⃣ 水印恢复在同一个微任务/宏任务内 → 截图都来不及


方案三:CSS ::before/::after + Canvas Blob 的深层防御(高阶加固)

利用伪元素 content: url(...) 嵌入水印图片,配合 !importantpointer-events: none

javascript 复制代码
function createDeepWatermark(text) {
  const canvas = document.createElement('canvas');
  canvas.width = 400;
  canvas.height = 300;
  const ctx = canvas.getContext('2d');
  ctx.rotate(-20 * Math.PI / 180);
  ctx.font = '16px Microsoft YaHei';
  ctx.fillStyle = 'rgba(0, 0, 0, 0.06)';
  ctx.fillText(text, 40, 160);
  
  // 转为 Blob URL
  const dataUrl = canvas.toDataURL();
  
  // 注入一个带 !important 的样式,通过伪元素渲染
  const style = document.createElement('style');
  style.id = 'wm-core-style';
  style.textContent = `
    #app::before {
      content: '' !important;
      position: fixed !important;
      inset: 0 !important;
      background-image: url("${dataUrl}") !important;
      background-repeat: repeat !important;
      pointer-events: none !important;
      z-index: 2147483647 !important;
      opacity: 1 !important;
      display: block !important;
      visibility: visible !important;
    }
  `;
  document.head.appendChild(style);
  
  // 同样加上 MutationObserver 和定时器
  // ...(复用方案二逻辑)
}

为什么要用 ::before 伪元素?

  • DevTools 里无法直接「选中删除」伪元素
  • 即使删掉 <style> 标签轮询也能恢复
  • !important 压过大部分用户注入的样式

方案四:防御 Degreed 用户(高阶反调试)

针对会打断点、手动删 Observer 的高级用户:

javascript 复制代码
// 检测 DevTools 是否打开(简易版)
const devtools = /./;
devtools.toString = function() {
  applyWatermark(); // 每次在 Console 求值时重建水印
  return '';
};

// 重写 MutationObserver 的 disconnect 方法
const originalDisconnect = MutationObserver.prototype.disconnect;
MutationObserver.prototype.disconnect = function() {
  // 判断是否是我们的 observer 被断开
  if (this._isWatermarkObserver) {
    console.warn('⚠️ 水印防护: 禁止断开 Observer');
    return;
  }
  return originalDisconnect.call(this);
};

⚠️ 注意: 这种「反反调试」手段需要把握好度,过度反调试会影响正常开发体验,建议仅在内部敏感系统中使用。


要点总结

方案 防护等级 实现成本 用户能否破解
CSS fixed 覆盖 5 分钟 1 秒
Canvas 背景图 ⭐⭐ 15 分钟 需要改 CSS
Canvas + Observer ⭐⭐⭐⭐ 30 分钟 需要断点调试
深度防御全栈 ⭐⭐⭐⭐⭐ 1 小时 需要极高技术水平

实战建议:

  • 普通后台管理系统:方案二(Canvas + Observer)就够用了,覆盖 95% 用户
  • 高安全级别场景:方案三 + 方案四组合
  • 永远别只依赖前端水印:前端水印是「防君子不防小人」的威慑手段,真正保密靠后端水印(接口注入 + 图片服务器打码)
  • 截图的终极防御 :前端水印 + Document.exitFullscreen() 监听 + 页面失焦模糊处理 + 禁用右键/打印
  • 性能注意 :Canvas 生成水印不要每次都 toDataURL,缓存一次结果;Observer 回调尽量轻量,避免高频触发

最后一句大实话: 用户真想扒你的数据,截图后裁剪掉水印发给 AI 擦除就行了。前端水印的核心价值是 「事后追溯」------每张泄露的截图上都有工号水印,安全部门就能找到「内鬼」。在 DOM 层面删掉水印只是「掩耳盗铃」,服务器端可以通过 HTTP 日志 + 请求指纹精确定位到人。

相关推荐
西安小哥5 分钟前
破局与重生:大厂前端如何借力 AI 转型“超级全栈“
前端·人工智能
sunly_35 分钟前
TypeScript总结:16、面向对象速查
前端·javascript·typescript
MXN_小南学前端39 分钟前
React超长文本域中实现“返回顶部”浮动按钮
前端·javascript·react.js
学习嵌入式的小周1 小时前
Notepad++8.8.7下载安装(附安装包)
前端·notepad++
gs801401 小时前
解构 Cordis:面向“时空可组合性”的 TypeScript 元框架深度剖析
前端·javascript·typescript
岁岁种桃花儿2 小时前
Vue核心语法第一篇:Vue是什么?
前端·javascript·vue.js
观无2 小时前
若依EasyExcel实现单元格合并
开发语言·前端·javascript
愚公搬代码3 小时前
【愚公系列】《Web应用安全》001-VMware的安装
前端·安全
xiaohaiAIgeo3 小时前
【2026年】HG/T 20656-2024化工暖通空调设计规范:新版标准的变化与影响
java·前端·javascript·科普知识
立少→万能汉编3 小时前
用“立少→超文本”写静态网页,标签<倍>
服务器·前端·javascript