基于Web Component实现防篡改水印
Web Component?
这篇文章的关键可以分为3个部分,Web Component + 防篡改 + 水印生成。在使用Web Component之前,我已经通过MutationObserver实现了防篡改水印的第一版。但在了解到Web Component的特性之后,我认为也许用Web Component也不错,因为Web Component内部有钩子天然支持被篡改时被触发,用来防篡改非常方便。下面也会对MutationObserver与Web Component的实现做比较。
设计目标
设计一个hook用于插入水印至ref指定的组件中,被插入的水印节点应当不可篡改。
const { data: username } = useRequest(...)
const ref = useWatermark(username); // 水印组件会插入ref.current对应的DOM节点
return <div ref={ref}>
{children}
</div>
水印生成原理
创建一个div节点,将背景设置为一个透明的水印图片,将z-index设置为10000叠加在站点上即可实现水印效果。
生成水印图
水印图可以在前端直接生成,可以使用canvas通过命令绘制,或预声明svg字符串。我选用的是svg字符串生成,因为使用svg清晰度较高,也比较简单。将svgString定义好,再转换成base64即可。
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="400px" height="146px">
<text x="10px" y="73px"
text-anchor="start"
transform="rotate(-20 0 73)"
fill="rgba(40, 47, 56, 0.03)"
font-weight="500"
font-size="14"
font-family="Helvetica Neue,Helvetica,Arial,PingFang SC,Hiragino Sans GB,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif"
>
${content}
</text>
<text x="210px" y="146px"
text-anchor="start"
transform="rotate(-20 200 146)"
fill="rgba(40, 47, 56, 0.03)"
font-weight="500"
font-size="14"
font-family="Helvetica Neue,Helvetica,Arial,PingFang SC,Hiragino Sans GB,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif"
>
${content}
</text>
</svg>`;
const background = 'data:image/svg+xml;base64,' + window.btoa(svgString);
CSS与DIV布局
注意所有CSS都应该直接定义在div节点上,并且加上!important,这样能有效避免样式被覆盖。
position: absolute !important;
top: 0px !important;
right: 0px !important;
bottom: 0px !important;
left: 0px !important;
overflow: hidden !important;
display: block !important;
opacity: 1 !important;
z-index: 10000 !important;
pointer-events: none !important; // 可以防止鼠标事件被水印节点拦截
background: url(${background});
const node = document.createElement('div');
watermarkRef.current = node;
node.setAttribute(
'style',
`position: absolute !important; top: 0px !important; right: 0px !important; bottom: 0px !important; left: 0px !important; overflow: hidden !important; display: block !important; opacity: 1 !important; z-index: 10000 !important; pointer-events: none !important; background: url(${background});`
);
防篡改原理
MutationObserve
const observer = new MutationObserver(function (entries) {
// 因为MutationObserve没办法监听自己的属性是否被修改,自己是否被移除,只能监听父组件
// 通过对父组件观测,entry.type = 'attributes'可以判断水印节点的属性是否被修改
// entry.type = 'childList' 可以判断水印节点是否被移除
for (const entry of entries) {
if (
(entry.type === 'attributes' && entry.target === watermarkRef.current) ||
(entry.type === 'childList' && Array.from(entry.removedNodes).includes(watermarkRef.current))
) {
removeWatermark();
generateWaterMark();
return;
}
}
});
const config = { attributes: true, childList: true, subtree: true };
observer.observe(
parentNode, // 水印挂载到的父组件
config
);
Web Component
使用Web Component的好处就是可以直接绑定回调函数,检测自己是否被篡改或移除。
class WatermarkElement extends HTMLElement {
static observedAttributes = ["style"]; // 需要观测的属性
constructor() {
super();
}
connectedCallback() {
// 当组件挂载至dom中,可以用来初始化
}
disconnectedCallback() {
// 当组件从document中移除,用于检测水印被删除
}
adoptedCallback() {
// 当组件在document中被移动
// 若触发则表示水印被移动,可能失效
}
attributeChangedCallback(name, oldValue, newValue) {
// 若触发表示style属性被操作,可能是恶意篡改
}
}
customElements.define("watermark-element", MyCustomElement);
转载自:https://juejin.cn/post/7314152331217551411