从破解某设计网站谈前端水印(详细教程),kotlin实战

首先我们来生成一个水印块,就是上面的 一个个秋风的笔记。这里主要有一点就是设置一个透明度(为了让水印看起来不是那么明显,从而不遮挡我们的主要页面),另一个就是一个旋转,如果是正的水平会显得不是那么好看,最后一点就是使用 userSelect 属性,让此时的文字无法被选中。

userSelect

CSS[4] 属性 user-select 控制用户能否选中文本。除了文本框内,它对被载入为 chrome[5] 的内容没有影响。

function cssHelper(el, prototype) {

for (let i in prototype) {

el.style[i] = prototype[i]

}

}

const item = document.createElement(‘div’)

item.innerHTML = ‘秋风的笔记’

cssHelper(item, {

position: ‘absolute’,

top: 50px,

left: 50px,

fontSize: 16px,

color: ‘#000’,

lineHeight: 1.5,

opacity: 0.1,

transform: rotate(-15deg),

transformOrigin: ‘0 0’,

userSelect: ‘none’,

whiteSpace: ‘nowrap’,

overflow: ‘hidden’,

})

有了一个水印片,我们就可以通过计算屏幕的宽高,以及水印的大小来计算我们需要生成的水印个数。

const waterHeight = 100;

const waterWidth = 180;

const { clientWidth, clientHeight } = document.documentElement || document.body;

const column = Math.ceil(clientWidth / waterWidth);

const rows = Math.ceil(clientHeight / waterHeight);

for (let i = 0; i < column * rows; i++) {

const wrap = document.createElement(‘div’);

cssHelper(wrap, Object.create({

position: ‘relative’,

width: ${waterWidth}px,

height: ${waterHeight}px,

flex: 0 0 ${waterWidth}px,

overflow: ‘hidden’,

}));

wrap.appendChild(createItem());

waterWrapper.appendChild(wrap)

}

document.body.appendChild(waterWrapper)

这样子我们就完美地实现了上面我们给出的思路的样子啦。

背景图实现

canvas

canvas的实现很简单,主要是利用canvas 绘制一个水印,然后将它转化为 base64 的图片,通过canvas.toDataURL() 来拿到文件流的 url ,关于文件流相关转化可以参考我之前写的文章一文带你层层解锁「文件下载」的奥秘, 然后将获取的 url 填充在一个元素的背景中,然后我们设置背景图片的属性为重复。

.watermark {

position: fixed;

top: 0px;

right: 0px;

bottom: 0px;

left: 0px;

pointer-events: none;

background-repeat: repeat;

}

function createWaterMark() {

const angle = -20;

const txt = ‘秋风的笔记’

const canvas = document.createElement(‘canvas’);

canvas.width = 180;

canvas.height = 100;

const ctx = canvas.getContext(‘2d’);

ctx.clearRect(0, 0, 180, 100);

ctx.fillStyle = ‘#000’;

ctx.globalAlpha = 0.1;

ctx.font = 16px serif

ctx.rotate(Math.PI / 180 * angle);

ctx.fillText(txt, 0, 50);

return canvas.toDataURL();

}

const watermakr = document.createElement(‘div’);

watermakr.className = ‘watermark’;

watermakr.style.backgroundImage = url(${createWaterMark()})

document.body.appendChild(watermakr);

svg

svg 和 canvas 类似,主要还是生成背景图片。

function createWaterMark() {

const svgStr =

`

<text x=“0px” y=“30px” dy=“16px”

text-anchor=“start”

stroke=“#000”

stroke-opacity=“0.1”

fill=“none”

transform=“rotate(-20)”

font-weight=“100”

font-size=“16”

秋风的笔记

`;

return data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))};

}

const watermakr = document.createElement(‘div’);

watermakr.className = ‘watermark’;

watermakr.style.backgroundImage = url(${createWaterMark()})

document.body.appendChild(watermakr);

明水印的破解一

以上就很快实现了水印的几种方案。但是对于有心之人来说,肯定会想着破解,以上破解也很简单。

打开了Chrome Devtools 找到对应的元素,直接按 delete 即可删除。

明水印的防御

这样子的水印对于大概知道控制台操作的小白就可以轻松破解,那么有什么办法能防御住这样的操作呢?

答案是肯定的,js 有一个方法叫做 MutationObserver,能够监控元素的改动。

MutationObserver 对现代浏览的兼容性还是不错的,MutationObserver是元素观察器,字面上就可以理解这是用来观察Node(节点)变化的。MutationObserver是在DOM4规范中定义的,它的前身是MutationEvent事件,最低支持版本为 ie9 ,目前已经被弃用。

在这里我们主要观察的有三点

  • 水印元素本身是否被移除

  • 水印元素属性是否被篡改(display: none …)

  • 水印元素的子元素是否被移除和篡改 (element生成的方式 )

来通过 MDN 查看该方法的使用示例。

const targetNode = document.getElementById(‘some-id’);

// 观察器的配置(需要观察什么变动)

const config = { attributes: true, childList: true, subtree: true };

// 当观察到变动时执行的回调函数

const callback = function(mutationsList, observer) {

// Use traditional ‘for loops’ for IE 11

for(let mutation of mutationsList) {

if (mutation.type === ‘childList’) {

console.log(‘A child node has been added or removed.’);

}

else if (mutation.type === ‘attributes’) {

console.log(‘The ’ + mutation.attributeName + ’ attribute was modified.’);

}

}

};

// 创建一个观察器实例并传入回调函数

const observer = new MutationObserver(callback);

// 以上述配置开始观察目标节点

observer.observe(targetNode, config);

MutationObserver主要是监听子元素的改动,因此我们的监听对象为 document.body, 一旦监听到我们的水印元素被删除,或者属性修改,我们就重新生成一个。通过以上示例,加上我们的思路,很快我们就写一个监听删除元素的示例。(监听属性修改也是类似就不一一展示了)

// 观察器的配置(需要观察什么变动)

const config = { attributes: true, childList: true, subtree: true };

// 当观察到变动时执行的回调函数

const callback = function (mutationsList, observer) {

// Use traditional ‘for loops’ for IE 11

for (let mutation of mutationsList) {

mutation.removedNodes.forEach(function (item) {

if (item === watermakr) {

document.body.appendChild(watermakr);

}

});

}

};

// 监听元素

const targetNode = document.body;

// 创建一个观察器实例并传入回调函数

const observer = new MutationObserver(callback);

// 以上述配置开始观察目标节点

observer.observe(targetNode, config);

我们打开控制台来检验一下。

这回完美了,能够完美抵御一些开发小白了。

那么这样就万无一失了吗?显然,道高一尺魔高一丈,毕竟前端的一切都是不安全的。

明水印的破解二

在一个高级前端工程师面前,一切都是纸老虎。接下来我就随便介绍三种破解的方式。

第一种

打开Chrome Devtools,点击设置 - Debugger - Disabled JavaScript .

然后再打开页面,delete我们的水印元素。

第二种

复制一个 body 元素,然后将原来 body 元素的删除。

第三种

打开一个代理工具,例如 charles,将生成水印相关的代码删除。

破解实践

接下来我们实战一下,通过预先分析,我们看到某在线设计网站的内容是以 div 的方式实现的,所以可以利用这种方案。打开 https://www.gaoding.com/design?id=33931419&simple=1  (仅供举例学习)

打开控制台,Ctrl + F 搜索 watermark 相关字眼。(这一步是作为一个程序员的直觉,基本上你要找什么,搜索对应的英文就可以 ~)

很快我们就找到了水印图。发现直接删除,没有办法删除水印元素,根据我们刚才学习的,肯定是利用了MutationObserver  方法。我们使用我们的第一个破解方法,将 JavaScript 禁用,再将元素删除。

水印已经消失了。

但是这样真的就万事大吉了吗?

不知道你有没有听过一种东西,看不见摸不着,但是它却真实存在,他的名字叫做暗水印,我们将时间倒流到 16 年间的月饼门事件,因为有员工将内网站点截图了,但是很快被定位出是谁截图了。

虽然你将一些可见的水印去除了,但是还会存在一些不可见的保护版权的水印。(这就是防止一些坏人拿去作另外的用途)

暗水印


暗水印是一种肉眼不可见的水印方式,可以保持图片美观的同时,保护你的资源版权。

暗水印的生成方式有很多,常见的为通过修改RGB 分量值的小量变动、DWT、DCT 和 FFT 等等方法。

通过介绍前端实现 RGB 分量值的小量变动 来揭秘其中的奥秘,主要参考 不能说的秘密——前端也能玩的图片隐写术 | AlloyTeam[7]。

我们都知道图片都是有一个个像素点构成的,每个像素点都是由 RGB 三种元素构成。当我们把其中的一个分量修改,人的肉眼是很难看出其中的变化,甚至是像素眼的设计师也很难分辨出。

你能看出其中的差别吗?根据这个原理,我们就来实践吧。(女孩子可以掌握方法后可以拿以下图片进行试验测试)

首先拿到以上图片,我们先来讲解解码方式,解码其实很简单,我们需要创建一个规律,再通过我们的规律去解码。现在假设的规律为,我们将所有像素的 R 通道的值为奇数的时候我们创建的通道密码,举个简单的例子。

image-20201128220542389

例如我们把以上当做是一个图形,加入他要和一个中文的 “一” 放进图像,例如我们将 “一” 放入第二行。按照我们的算法,我们的图像会变成这个样子。

image-20201128220833657

解码的时候,我们拿到所有的奇数像素将它渲染出来,例如这里的 ‘5779’ 是不是正好是一个 “一”,下面就转化为实践。

解码过程

首先创建一个 canvas 标签。

var ctx = document.getElementById(‘canvas’).getContext(‘2d’);

var img = new Image();

var originalData;

img.onload = function () {

// canvas像素信息

ctx.drawImage(img, 0, 0);

originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);

console.log()

processData(ctx, originalData)

};

img.src = ‘qiufeng-super.png’;

我们打印出这个数组,会有一个非常大的数组,一共有 256 * 256 * 4 = 262144 个值。因为每个像素除了 RGB 外还有一个 alpha 通道,也就是我们常用的透明度。

image-20201128215615494

上面也说了,我们的 R 通道为奇数的时候 ,就我们的解密密码。因此我们只需要所有的像素点的 R 通道为奇数的时候,将它填填充,不为奇数的时候就不填充,很快我们就能得到我们的隐藏图像。

var processData = function (ctx, originalData) {

var data = originalData.data;

for (var i = 0; i < data.length; i++) {

if (i % 4 == 0) {

// R分量

if (data[i] % 2 == 0) {

data[i] = 0;

} else {

data[i] = 255;

}

} else if (i % 4 == 3) {

// alpha通道不做处理

continue;

} else {

// 关闭其他分量,不关闭也不影响答案

data[i] = 0;

}

}

// 将结果绘制到画布

ctx.putImageData(originalData, 0, 0);

}

processData(ctx, originalData)

解密完会出现类似于以下这个样子。

那我们如何加密的,那就相反的方式就可以啦。(这里都用了 不能说的秘密——前端也能玩的图片隐写术[8] 中的例子,= = 我也能写出一个例子,但是觉得没必要,别人已经写得很好了,我们只是讲述这个方法,需要代码来举例而已)

编码过程

加密呢,首先我们需要获取加密的图像信息。

var textData;

var ctx = document.getElementById(‘canvas’).getContext(‘2d’);

ctx.font = ‘30px Microsoft Yahei’;

ctx.fillText(‘秋风的笔记’, 60, 130);

textData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;

然后提取加密信息在待加密的图片上进行处理。

var mergeData = function (ctx, newData, color, originalData) {

var oData = originalData.data;

var bit, offset;  // offset的作用是找到alpha通道值,这里需要大家自己动动脑筋

switch (color) {

case ‘R’:

bit = 0;

offset = 3;

break;

case ‘G’:

bit = 1;

offset = 2;

break;

case ‘B’:

bit = 2;

offset = 1;

break;

}

for (var i = 0; i < oData.length; i++) {

if (i % 4 == bit) {

// 只处理目标通道

if (newData[i + offset] === 0 && (oData[i] % 2 === 1)) {

// 没有信息的像素,该通道最低位置0,但不要越界

if (oData[i] === 255) {

oData[i]–;

} else {

oData[i]++;

}

} else if (newData[i + offset] !== 0 && (oData[i] % 2 === 0)) {

// // 有信息的像素,该通道最低位置1,可以想想上面的斑点效果是怎么实现的

oData[i]++;

}

}

}

ctx.putImageData(originalData, 0, 0);

}

主要的思路还是我一开始所讲的,在有像素信息的点,将 R 偶数的通道+1。在没有像素点的地方将 R 通道转化成偶数,最后在 img.onload 调用 processData(ctx, originalData)

img.onload = function () {

// 获取指定区域的canvas像素信息

ctx.drawImage(img, 0, 0);

originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);

console.log(originalData)

processData(ctx, originalData)

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

js基础

1)对js的理解?
2)请说出以下代码输出的值?
3)把以下代码,改写成依次输出0-9
4)如何区分数组对象,普通对象,函数对象
5)面向对象、面向过程
6)面向对象的三大基本特性
7)XML和JSON的区别?
8)Web Worker 和webSocket?
9)Javascript垃圾回收方法?
10)new操作符具体干了什么呢?
11)js延迟加载的方式有哪些?
12)WEB应用从服务器主动推送Data到客户端有那些方式?

js基础.PNG

前16.PNG

天花板技术停滞不前!**

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-CroevYoT-1712003140872)]
[外链图片转存中…(img-uUh3jKEy-1712003140872)]
[外链图片转存中…(img-Pdp526Wp-1712003140873)]
[外链图片转存中…(img-ft434BBZ-1712003140873)]
[外链图片转存中…(img-oqvRG0wP-1712003140874)]
[外链图片转存中…(img-P66GW8Yy-1712003140874)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-sUO4B6Eu-1712003140874)]

js基础

1)对js的理解?
2)请说出以下代码输出的值?
3)把以下代码,改写成依次输出0-9
4)如何区分数组对象,普通对象,函数对象
5)面向对象、面向过程
6)面向对象的三大基本特性
7)XML和JSON的区别?
8)Web Worker 和webSocket?
9)Javascript垃圾回收方法?
10)new操作符具体干了什么呢?
11)js延迟加载的方式有哪些?
12)WEB应用从服务器主动推送Data到客户端有那些方式?

[外链图片转存中…(img-9FEUz4yz-1712003140875)]

[外链图片转存中…(img-fftqBVqq-1712003140875)]

CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值