浅析Web前端水印方案:前端加水印和服务端加水印的适用场景、不同的实现方案、使用 MutationObserver 监听dom元素变化、MutationObserver API 介绍

摘要:
对页面和图像添加水印处理是非常必要的。对于掌握某些前端知识的人,他们可以跳过水印,通过各种操作获得源文件。水印通过模糊和其他操作进行攻击和消除。

一、问题背景

  为了防止信息泄露或知识产权被侵犯,在web的世界里,对于页面和图片等增加水印处理是十分有必要的,水印的添加根据环境可以分为两大类,前端浏览器环境添加和后端服务环境添加,根据实现方式又可以分为两大类,显性水印数字水印。简单对比一下这两种方式的特点:

1、前端浏览器加水印:

(1)减轻服务端的压力,快速反应

(2)安全系数较低,对于掌握一定前端知识的人来说可以通过各种骚操作跳过水印获取到源文件

(3)适用场景:资源不跟某一个单独的用户绑定,而是一份资源,多个用户查看,需要在每一个用户查看的时候添加用户特有的水印,多用于某些机密文档或者展示机密信息的页面,水印的目的在于文档外流的时候可以追究到责任人

2、后端服务器加水印:

(1)当遇到大文件密集水印,或是复杂水印,占用服务器内存、运算量,请求时间过长

(2)安全性高,无法获取到加水印前的源文件

(3)适用场景:资源为某个用户独有,一份原始资源只需要做一次处理,将其存储之后就无需再次处理,水印的目的在于标示资源的归属人。

3、显性水印:容易处理,算法较为简单;可以通过裁剪、模糊等操作对水印进行攻击消除,同时显性水印也会破坏图片的完整性。

4、数字水印:算法一般较为复杂,抗攻击能力较强。

  当然,优缺点也需要分情况来看,各个方案都拥有自己的优缺点,需要使用者在安全性、性能之间衡量。没有最好的方案,只有根据环境与需求,使用当前最适合的方案。

二、调研 - 看看其他网站如何处理的

  比如:CSDN、知乎、微博,都是直接 img 显示 url,当获取到 url 在浏览器下打开的时候,获取到的是已经添加水印的图片。(也有可能水印应该不止所见)

  • 后端直接处理增加水印(前端直接使用img标签显示url
  • 暂时只能看到表层右下角水印(用户名)
  • 是否添加数字水印(未知)

  从这三个网站的特点来看,这种做法是很合适的,因为他们需要添加水印的资源往往都绑定到了某一个用户上,也就是,一份原始资源只需要做一次处理(前后端都可),将其存储之后就无需再次处理。

  但是,这种方式在某些情况下是却不是最佳的。比如资源不跟某一个单独的用户绑定,而是一份资源,多个用户查看,需要在每一个用户查看的时候添加用户特有的水印。这多用于某些机密文档或者内部文件,水印的目的在于文档外流的时候可以追究到责任人。故而,添加水印的方法需要根据环境的不同需求的不同来变化。

三、实现方案

1、显性水印 + DOM元素直接遮盖 - 重复的 dom 元素覆盖实现

  从效果开始,要实现的效果是「在页面上充满透明度较低的重复的代表身份的信息」,第一时间想到的方案是在页面上覆盖一个 position:fixed 的div盒子,盒子透明度设置较低,设置  pointer-events: none;样式实现点击穿透,在这个盒子内通过 js 循环生成小的水印div,每个水印div内展示一个要显示的水印内容。

  页面效果是有了,但是这种方案需要要在js内循环创建多个dom元素,既不优雅也影响性能,于是考虑可不可以不生成这么多个元素。

  将水印文字直接通过一层DOM元素,覆盖到需要添加水印的图片上,并且可以添加两层,一层为明显水印,其透明度较高,肉眼可见,一层为隐藏水印透明度极低,肉眼无法分辨,但可以通过一些处理后续显现出来(以PS为例:现在把图片放到PS里面,建一个图层在上面,全部填充为黑色,混合模式选择颜色加深这一类的(也就是让亮的更亮,暗的更暗))

  这样,在用户截图的之后,就算涂抹掉了明显水印,可由于隐藏水印肉眼无法分辨,简单的涂抹攻击并不能准确定位到隐藏水印。

  当对于图片完整性要求不高(也就是铺满了水印都不介意,只要看清内容即可)的情况,建议增加水印密度,直到只要用户去涂抹水印,就会直接破坏文件到无法阅读的地步

2、显性水印+Canvas:canvas 输出背景图,或 svg 实现背景图

  第一步还是在页面上覆盖一个固定定位的盒子,然后创建一个canvas画布,绘制出一个水印区域,将这个水印通过toDataURL方法输出为一个图片,将这个图片设置为盒子的背景图,通过backgroud-repeat:repeat;样式实现填满整个屏幕的效果。

  与canvas生成背景图的方法类似,只不过是生成背景图的方法换成了通过svg生成,canvas的兼容性略好于svg。

  其实现和显性水印+DOM元素直接遮盖一样,但其性能优于方案一,直接通过Canvas绘画,避免了在水印密度较大的情况下大量DOM元素的创建与添加,并且Canvas在部分环境与浏览器下拥用GPU加速的功能,故而性能提升较大。

  具体一些实现代码参考,可以看这篇文章:https://mp.weixin.qq.com/s/7NxQMtolD3UL5qDBsDkIWw

四、如何防止删除 dom 元素去除水印

  这样看起来能满足我们的需求了,但是还有一个问题,稍微懂一点浏览器的使用或网页知识的用户,可以用浏览器的开发者工具来动态更改DOM的属性或者结构就可以去掉了。我们可以使用 MutationObserver 来监听 dom 元素变化MutationObserver给开发者们提供了一种能在某个范围内的DOM树发生变化时做出适当反应的能力。

  MutationObserver兼容性可以看出高级浏览器以及移动浏览器支持非常不错。突变观察员 API 用来监视DOM变动。DOM的任何变动,比如节点的增减,属性的变动,文本内容的变动,这个 API 都可以得到通知。

  使用MutationObserver的实例的观察函数方法用来启动监听,它接受两个参数:第一个参数:所要观察的DOM节点,第二个参数:一个配置对象,指定所要观察的特定变动,有以下几种:

属性描述
childList如果需要观察目标节点的子节点(新增了某个子节点,或者移除了某个子节点),则设置为true.
attributes如果需要观察目标节点的属性节点(新增或删除了某个属性,以及某个属性的属性值发生了变化),则设置为true.
characterData如果目标节点为characterData节点(一种抽象接口,具体可以为文本节点,注释节点,以及处理指令节点)时,也要观察该节点的文本内容是否发生变化,则设置为true.
subtree除了目标节点,如果还需要观察目标节点的所有后代节点(观察目标节点所包含的整棵DOM树上的上述三种节点变化),则设置为true.
attributeOldValue在attributes属性已经设为true的前提下,如果需要将发生变化的属性节点之前的属性值记录下来(记录到下面MutationRecord对象的oldValue属性中),则设置为true.
characterDataOldValue在characterData属性已经设为true的前提下,如果需要将发生变化的characterData节点之前的文本内容记录下来(记录到下面MutationRecord对象的oldValue属性中),则设置为true.
attributeFilter一个属性名数组(不需要指定命名空间),只有该数组中包含的属性名发生变化时才会被观察到,其他名称的属性发生变化后会被忽略.

  MutationObserver只能监测到某种属性改变,增减子结点等,对于自己本身被删除,是没有办法的,可以通过监测父结点来达到要求。检测代码实现如下:

const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
if (MutationObserver) {
  let mo = new MutationObserver(function () {
    const __wm = document.querySelector('.__wm');
    // 只在__wm元素变动才重新调用 __canvasWM
    if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
      // 避免一直触发
      mo.disconnect();
      mo = null;
      __canvasWM(JSON.parse(JSON.stringify(args)));
    }
  });
  mo.observe(container, {
    attributes: true,
    subtree: true,
    childList: true
  })
}

  Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。

  概念上,它很接近事件,可以理解为 DOM 发生变动就会触发 Mutation Observer 事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说,DOM 的变动立刻会触发相应的事件;Mutation Observer 则是异步触发,DOM 的变动并不会马上触发,而是要等到当前所有 DOM 操作都结束才触发

  这样设计是为了应付 DOM 变动频繁的特点。举例来说,如果文档中连续插入1000个<p>元素,就会连续触发1000个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;而 Mutation Observer 完全不同,只在1000个段落都插入结束后才会触发,而且只触发一次。

  Mutation Observer 有以下特点:

(1)它等待所有脚本任务完成后,才会运行(即异步触发方式)。

(2)它把 DOM 变动记录封装成一个数组进行处理,而不是一条条个别处理 DOM 变动。

(3)它既可以观察 DOM 的所有类型变动,也可以指定只观察某一类变动。

  关于 Mutation Observer API 的详细介绍及具体如何使用,可以看这篇文章:《Mutation Observer API》- http://javascript.ruanyifeng.com/dom/mutationobserver.html

免责声明:文章转载自《浅析Web前端水印方案:前端加水印和服务端加水印的适用场景、不同的实现方案、使用 MutationObserver 监听dom元素变化、MutationObserver API 介绍》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇如何使用记事本编程,并生成exe设置最佳线程数总结下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Css 浮动高度问题_Css浮动兄弟元素高度问题

一、Css 启用浮动高度问题整理 1.Css启用浮动后,父元素高度塌陷。解决方案 使用clear增加清楚浮动来处理。 2.Css 指定高度的Div浮动,自适应的兄弟元素默认等高。想要自适应,兄弟元素使用  overflow: hidden; 二、Css浮动兄弟元素高度问题 现象: 浮动元素指定了高度,非浮空元素想要高度自适应。   指定高度的left ,...

selenium元素定位之多个元素中选择其中的一个

1、xpath匹配到多个元素如何选择指定的元素定位(1)xpath匹配到多个元素,选择指定的元素,通过末尾的数字选择具体的元素 (//span[@id=‘example’])[2] (2)xpath定位到统计标签的最后一个 //span[@id='example']/a[last()] (3)xpath定位同级标签的倒数第二个 //span[@id='ex...

WPF下的右键菜单隐藏

WPF中,右键菜单一旦设置,就很难控制其不让打开,点击右键一定会弹出,如果只是隐藏几个项还是没问题的,但是如果所有项都隐藏了,还是会弹出一个空白内容的右键菜单,难看死,也没有给任何后台控制的方法,于是找到了一个前台的属性可以控制。 <Window x:Class="WPFTest.Window1" xmlns="http://sche...

springboot之jackson的两种配置方式

springboot 针对jackson是自动化配置的,如果需要修改,有两种方式: 方式一:通过application.yml 配置属性说明:## spring.jackson.date-format指定日期格式,比如yyyy-MM-dd HH:mm:ss,或者具体的格式化类的全限定名 spring.jackson.deserialization是否开启J...

XAML学习笔记——Layout(四)

SplitView   SplitView简介   在学习SplitView之前,我们需要明确它的重要性。SplitView在UWP中有很重要的地位,它是UWP响应式布局主要技术,更是作为非常多UWP项目的核心布局。现在绝大部分win10应用的主框架布局都为SplitView,先看一个在win10中常见的系统设置窗口:   截图中,主窗体被分为两部分,一部...

[Data Structure &amp;amp; Algorithm] 八大排序算法

  排序有内部排序和外部排序之分,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。我们这里说的八大排序算法均为内部排序。   下图为排序算法体系结构图:   常见的分类算法还可以根据排序方式分为两大类:比较排序和非比较排序。本文中前七种算法都是比较排序,非比较排序有三种,分别为:  ...