研究明水印总结

115 阅读3分钟

产生这一文档的原因来自本人上家单位的产品里有明水印这一需求,但是查找到对应元素后水印可被移除,然后和咱们产品的水印相对比发现咱们明水印无法移除,感受上更为严谨,所以将仔细研究后的内容作出总结。

为什么要使用水印

  • 对于公司机密性文件,员工有可阅读的权限,为防止员工泄漏机密而展示,方便订阅到操作泄漏的人
  • 为标明作者,防止窃取他人努力结果而添加的

如何创建水印

元素绘制法

使用元素进行全局平铺的方式对DOM树上的文本节点,绘制压力等都有一定的影响,因此我们不建议这么进行,有兴趣的人可以自己搜搜看看

svg绘制法

和下方canvas绘制原理相似,这里不做过多描述

canvas绘制法

这里是本人写的一个demo

使用canvas绘制的好处
  • canvas消耗GPU,对CPU消耗小,绘制快
  • dom元素数量会少,减少对DOM树的渲染压力
  • 使用css进行操作,速度快,效果好
绘制步骤
  1. 首先我们需要一个具体的画布进行绘制,这里的canvas必须存在在界面上,否则无法进行绘制,并在绘制结束生成链接地址

    1. 首先我们会通过定义旋转角度,名称,水印字体的大小而绘制生成以下的一个图片,此时的canvas元素在绘制时我们不期望它可见,在绘制初期需要将其移出界面
       // 绘制生成水印图片
          function createWaterMark({ name = "", angle = -20, fontSize = 16 }) {
            const txt = name;
            // canvas必须在界面存在才可进行绘制
            const canvas = document.createElement("canvas");
            document.body.appendChild(canvas);
            // 长度计算
            const width = fontSize * txt.length;
            // 角度旋转计算,  这里不建议超过45度以上的旋转,所以取模
            const angles = (Math.PI / 180) * (angle % 45);
            // 高度计算,这里用到三角函数计算,防止不同长度展示差异过大
            const height =
              Math.ceil(Math.sqrt(Math.abs(Math.sin(angles)) * 100 * width)) +
              fontSize;
            // 因为绘制图片会旋转canvas图层,所以要计算旋转后不可见区域的长,增宽绘布
            const discrepancy =
              Math.ceil(Math.sqrt(Math.abs(Math.sin(angles)) * 100 * height * 2)) +
              fontSize;
            // 定宽高
            canvas.width = width * 2 + discrepancy;
            canvas.height = height * 2;
                 // 写canvas样式移出界面,防止还未移除canvas时的闪屏
            canvas.style.cssText = `
            width: ${width * 2 + discrepancy}px;
            height: ${height * 2}px;
            position: fixed;
            bottom: -100%;
          `;
            // 绘制
            const ctx = canvas.getContext("2d");
            // 清理绘布
            ctx.clearRect(0, 0, 180, 100);
            // 旋转
            ctx.rotate(angles);
            ctx.beginPath();
            ctx.fillStyle = "#000";
            // 透明度
            ctx.globalAlpha = 0.1;
            // 字体
            ctx.font = `${fontSize}px serif`;
            ctx.fillText(txt, 0, height);
            ctx.stroke();
            ctx.beginPath();
            ctx.fillStyle = "#f00";
            ctx.fillText(txt, width, height * 2);
            ctx.stroke();
          const urls = canvas.toDataURL();
            return urls;
          }
    
      ```
      
    
        2. 绘制完成后canvas元素不被我们需要,我们就可以执行 `canvas.remove();`的操作
    
    
  2. 创建元素平铺界面,生成水印效果

为了使水印可见且可以达到点击穿透的效果,我们需要在css里注意z-index的设置和pointer-events: none;的设置

image.png

    // 创建放置水印的方式
    function putWaterMark({ name = "", angle = -20, fontSize = 16 }) {
      // 创建元素塞入界面,这里可将应用获取名称操作前置,所以没有拆分很细,否则建议可以内存canvasUrl的地址
      const watermark = document.createElement("div");
      watermark.className = "watermark";
      // 背景图默认平铺
      watermark.style.backgroundImage = `url(${createWaterMark({
        name,
        angle,
        fontSize,
      })})`;
      document.body.appendChild(watermark);
    }
    // 绘制方式抽离,方便监听处调用
    const initWaterMark = () => {
      putWaterMark({
        name: "猴霸天",
      });
    };
    // 初识调用
    initWaterMark();

3. 兜底不法的水印移除操作

这里主要为了一些懂一些代码的人直接删除水印元素而定,其余的特殊情况需要借助暗水印

 // 监听元素改变,预防水印直接操作删除
    // 创建一个 MutationObserver 对象
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === "childList" && mutation.removedNodes.length > 0) {
          // 当被删除元素为水印时重新执行填入操作
          if (mutation.removedNodes[0].className === "watermark") {
            initWaterMark();
          }
        }
      });
    });
    // 选择要监听的目标元素
    const targetElement = document.querySelector(".watermark");
    const parentNode = targetElement.parentElement;
    // 开始观察目标元素的子节点变化
    observer.observe(parentNode, {
      childList: true,
    });

一些异常行为(别瞎用哈)

  • 直接搜索删除watermark ==》 这里就是本篇上述的方式,用observe监听给水印在添加上去
  • 复制body删除原始body
  • Chrome Devtools的Debuger禁用js,再操作删除watermark
  • 抓包更改原始代码取消监听

以上除了第一种以外,其他都是我们不可控的一些行为,这时候就要用到暗水印,一般分两种,前端绘制页面绘制和oss桶写入时绘制,感兴趣的可以自己搜搜,方法很多,对安全性来说更保险。