Front-end watermarking generation scheme
Some time ago to do a system audit background, audit personnel screenshots of the content of the leak, although the screenshots are not particularly sensitive, but the security problem can not be ignored. So in the system page above the watermark, screenshots for auditors and other sensitive operations have a certain prompt role.
Web watermark generation solution
Generate watermark through canvas
Canvas compatibility
Here, we use Canvas to generate base64 pictures and check compatibility through CanIUse website. If it is used in mobile terminal and some management systems, compatibility problems can be completely ignored.
HTMLCanvasElement. ToDataURL () method returns a data URI contains pictures show. You can use the type parameter, which is PNG by default. The image resolution is 96dpi.
If the height or width of the canvas is 0, the string “data:,” is returned. If the type passed is not “image/ PNG”, but the value returned begins with “data:image/ PNG”, the type passed is not supported. Chrome supports the image/ Webp type. Specific reference HTMLCanvasElement. ToDataURL
The specific code is as follows:
(function () {// canvas implements watermark function __canvasWM({// canvas uses ES6 function default to set default values of parameters // see the details https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters container = document.body, width = '200px', height = '150px', textAlign = 'center', textBaseline = 'middle', Font = "20px Microsoft yahei", fillStyle = 'rgba(184, 184, 184, 0.8)', content = 'do not rotate ', rotate = '30', zIndex = 1000 } = {}) { var args = arguments[0]; var canvas = document.createElement('canvas'); canvas.setAttribute('width', width); canvas.setAttribute('height', height); var ctx = canvas.getContext("2d"); ctx.textAlign = textAlign; ctx.textBaseline = textBaseline; ctx.font = font; ctx.fillStyle = fillStyle; ctx.rotate(Math.PI / 180 * rotate); ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2); var base64Url = canvas.toDataURL(); const watermarkDiv = document.createElement("div"); watermarkDiv.setAttribute('style', ` position:absolute; top:0; left:0; width:100%; height:100%; z-index:${zIndex}; pointer-events:none; background-repeat:repeat; background-image:url('${base64Url}')`); container.style.position = 'relative'; container.insertBefore(watermarkDiv, container.firstChild); }); window.__canvasWM = __canvasWM; }) (); // call __canvasWM({content: 'QQMusicFE'})Copy the code
The effect is as follows:! Canvas achieves web watermarking effect
To make this method more generic and compatible with different references, we can also add this code:
// For compatibility with different environments if (typeof Module! = 'undefined' && module.exports) { //CMD module.exports = __canvasWM; } else if (typeof define == 'function' && define.amd) { // AMD define(function () { return __canvasWM; }); } else { window.__canvasWM = __canvasWM; }Copy the code
This seems to meet our needs, but there is a problem, a little browser or web knowledge users can use the browser developer tools to dynamically change the DOM properties or structure can be removed. There are two solutions:
- Monitor the changes in the watermark div, recording the innerHTML of the newly generated div, taking a new value every few seconds, and regenerating the watermark once it changes. But this approach can affect performance;
- Using MutationObserver
MutationObserver gives developers the ability to react appropriately to changes in a DOM tree within a range.
MutationObserver compatibility
As you can see from the compatibility table, advanced browser and mobile browser support is pretty good. The Mutation Observer API is used to monitor DOM changes. The API is notified of any changes to the DOM, such as the addition or subtraction of nodes, changes in attributes, and changes in text content. Using the MutationObserver constructor, create a new observer instance that takes a callback function that takes two arguments, the first a variable array and the second an observer instance. The Observe method of an instance of MutationObserver is used to initiate listening and takes two arguments. The first parameter: the DOM node to observe, and the second parameter: a configuration object that specifies the specific changes to observe, which are as follows:
attribute | describe |
---|---|
childList | Set to true if you need to observe the children of the target node (a child is added or removed). |
attributes | Set to true if you need to observe the property node of the target node (an attribute is added or deleted, or the value of an attribute has changed). |
characterData | Set to True if the target node is a characterData node (an abstract interface that can be a text node, a comment node, or a processing instruction node) and the text content of that node is also observed to see if it has changed. |
subtree | In addition to the target node, set this parameter to true if you need to observe all descendants of the target node (see the above three node changes in the entire DOM tree that the target node contains). |
attributeOldValue | With the Attributes attribute already set to true, it is set to true if the value of the attribute node that changed needs to be recorded (in the oldValue attribute of the MutationRecord object below). |
characterDataOldValue | If the characterData property is already set to true, set it to true if the previous text of the characterData node that has changed needs to be recorded (in the oldValue property of the MutationRecord object below). |
attributeFilter | An array of property names (namespace-free) that is observed only when the property names in the array change and ignored when the properties with other names change. |
MutationObserver can only detect attribute changes, add or delete children, etc., there is no way to monitor the parent to achieve the requirements of its own deletion. So the code after the final transformation is:
(function () {// canvas implements watermark function __canvasWM({// canvas uses ES6 function default to set default values of parameters // see the details https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters container = document.body, width = '300px', height = '200px', textAlign = 'center', textBaseline = 'middle', Font = "20px Microsoft Yahei", fillStyle = 'rgba(184, 184, 184, 0.6)', content = 'do not rotate ', rotate = '30', zIndex = 1000 } = {}) { const args = arguments[0]; const canvas = document.createElement('canvas'); canvas.setAttribute('width', width); canvas.setAttribute('height', height); const ctx = canvas.getContext("2d"); ctx.textAlign = textAlign; ctx.textBaseline = textBaseline; ctx.font = font; ctx.fillStyle = fillStyle; ctx.rotate(Math.PI / 180 * rotate); ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2); const base64Url = canvas.toDataURL(); const __wm = document.querySelector('.__wm'); const watermarkDiv = __wm || document.createElement("div"); const styleStr = ` position:absolute; top:0; left:0; width:100%; height:100%; z-index:${zIndex}; pointer-events:none; background-repeat:repeat; background-image:url('${base64Url}')`; watermarkDiv.setAttribute('style', styleStr); watermarkDiv.classList.add('__wm'); if (! __wm) { container.style.position = 'relative'; container.insertBefore(watermarkDiv, container.firstChild); } const MutationObserver = window.MutationObserver || window.WebKitMutationObserver; if (MutationObserver) { let mo = new MutationObserver(function () { const __wm = document.querySelector('.__wm'); __canvasWM if ((__wm && __wm. GetAttribute ('style'))! == styleStr) || ! __wm) {// Avoid always triggering mo.disconnect(); mo = null; __canvasWM(JSON.parse(JSON.stringify(args))); }}); mo.observe(container, { attributes: true, subtree: true, childList: true }) } } if (typeof module ! = 'undefined' && module.exports) { //CMD module.exports = __canvasWM; } else if (typeof define == 'function' && define.amd) { // AMD define(function () { return __canvasWM; }); } else { window.__canvasWM = __canvasWM; }}) (); // call __canvasWM({content: 'QQMusicFE'});Copy the code
Generate watermarks using SVG
SVG: Scalable Vector Graphics (SVG) is a Graphics format based on extensible Markup Language (XML) for describing two-dimensional Vector Graphics. SVG, developed by the W3C, is an open standard. — Wikipedia
SVG Browser Compatibility
Compared with Canvas, SVG has better browser compatibility. The method of using SVG to generate watermarks is similar to that of Canvas, except that the generation method of base64Url is changed to SVG. Details are as follows:
Container = document.body, content = 'do not send ', width = '300px', Height = '200px', opacity = '0.2', fontSize = '20px', zIndex = 1000} = {}) {const args = arguments[0]; const svgStr = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${width}"> <text x="50%" y="50%" dy="12px" text-anchor="middle" stroke="#000000" stroke-width="1" stroke-opacity="${opacity}" fill="none" transform="rotate(-45, 120 120)" style="font-size: ${fontSize};" > ${content} </text> </svg>`; const base64Url = `data:image/svg+xml; base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`; const __wm = document.querySelector('.__wm'); const watermarkDiv = __wm || document.createElement("div"); / /... // same as canvas //... }) (); __svgWM({ content: 'QQMusicFE' })Copy the code
Generate the watermark through NodeJS
As a modern front-end developer, Node.js also needs to be mastered. Web watermarks can also be generated using NodeJS (or better yet, using user clients for performance reasons). The front end sends a request, the parameter carries watermark content, the background returns the picture content. Concrete implementation (Koa2 environment) :
- Install gm and related environment, see GM documentation for details
ctx.type = 'image/png';
Set the response to the image type- The image generation process is asynchronous, so a layer of Promise needs to be wrapped so that ctx.body can be assigned with async/await
const fs = require('fs') const gm = require('gm'); const imageMagick = gm.subClass({ imageMagick: true }); const router = require('koa-router')(); router.get('/wm', async (ctx, next) => { const { text } = ctx.query; ctx.type = 'image/png'; ctx.status = 200; ctx.body = await ((() => { return new Promise((resolve, reject) => { imageMagick(200, 100, "Rgba (255255255, 0)) fontSize (40) drawText (10, 50, text), write (the require (" path"). The join (__dirname, `. / ${text}. PNG `), function (err) { if (err) { reject(err); } else { resolve(fs.readFileSync(require('path').join(__dirname, `./${text}.png`))) } }); })}) ()); });Copy the code
If the watermark is simply displayed, you are advised to generate it in the browser for better performance
Image watermark generation solution
In addition to watermarking web pages, sometimes we need to watermark pictures, so that users save pictures, with the source of the watermark information, not only can protect copyright, other information of the watermark can also prevent leaks.
Watermarks images on canvas
The implementation is as follows:
(function() { function __picWM({ url = '', textAlign = 'center', textBaseline = 'middle', Font = "20px Microsoft Yahei", fillStyle = 'rgba(184, 184, 184, 0.8)', cb = null, textX = 100, textY = 30 } = {}) { const img = new Image(); img.src = url; img.crossOrigin = 'anonymous'; img.onload = function() { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); ctx.textAlign = textAlign; ctx.textBaseline = textBaseline; ctx.font = font; ctx.fillStyle = fillStyle; ctx.fillText(content, img.width - textX, img.height - textY); const base64Url = canvas.toDataURL(); cb && cb(base64Url); } } if (typeof module ! = 'undefined' && module.exports) { //CMD module.exports = __picWM; } else if (typeof define == 'function' && define.amd) { // AMD define(function () { return __picWM; }); } else { window.__picWM = __picWM; }}) (); / / call __picWM ({url: "http://localhost:3000/imgs/google.png", content: 'QQMusicFE, cb: (base64Url) => { document.querySelector('img').src = base64Url }, });Copy the code
The effect is as follows:
Canvas creates watermarks for images
Batch watermarking images with NodeJS
We can also use the GM library to watermark images
function picWM(path, text) { imageMagick(path) .drawText(10, 50, text) .write(require('path').join(__dirname, `./${text}.png`), function (err) { if (err) { console.log(err); }}); }Copy the code
If you need to batch images, you can simply iterate through the relevant files.
If the watermark is simply displayed, you are advised to generate it in the browser for better performance
expand
Hidden watermark
Some time ago, Ali identified the whistleblower of the mooncake incident with screenshots, which is actually using a hidden watermark. This is largely out of the realm of the front end, but it should be understood. AlloyTeam team wrote a can’t say secret – front end can also play image steganography images through the Canvas to add the “hidden watermark”, for the user to save images, can be easily restore hidden inside, but the inability to capture or processed photos, but photos for some confidential documents, It’s possible to use this technology secretly.
Use encrypted watermarked content
Front-end generation of watermark can also be, others can also use the same way to generate, there may be “blame on others” (may be this is overthinking), we still have to have a more secure solution. Watermark content can contain a variety of encoded information, including user name, user ID, time, etc. For example, if we just want to save the user’s unique user ID, we need to pass the user ID into the following MD5 method to generate the unique ID. The encoded information is irreversible, but can be traced by globally traversing all users. In this way, we can prevent watermark fraud and trace the real watermark information.
// MD5 exports-const utils = require('utility') // MD5 exports-const utils = function (content) {// MD5 exports-const utils = require('utility' 'microzz_asd! @#IdSDAS~~'; return utils.md5(utils.md5(content + salt)); }Copy the code
conclusion
Security issues can not be careless, for some sensitive content, we can use the above watermarking scheme by combination, so as to maximize the role of warning to the viewer, reduce the situation of leaks, even if the leaks, it is possible to track the leaks.
Refer to the link
-
Unspeakable secret – Picture steganography that can be played on the front end
-
Mutation Observer API
-
Lucifer – Image web watermarking scheme based on KM watermarking
-
Damon – Web watermarking front-end SVG implementation scheme