Front-end watermark generation scheme

Some time ago to do a system audit background, there was a screenshot of the content of the audit personnel leaked, although the screenshot content is not particularly sensitive, but the security problem can not be ignored. Therefore, the watermark is added to the system page, which can serve as a hint for sensitive operations such as screenshots of audit personnel.

Web page watermark generation solution

Generate watermark through Canvas

Canvas compatibility



Here we use Canvas to generate base64 images and check compatibility through CanIUse website. If used on 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 defaults to PNG. The resolution of the image is 96dpi.

If the height or width of the canvas is 0, the string “data:,” is returned. If the passed type is not “image/ PNG”, but the returned value begins with “data:image/ PNG”, then the passed type is not supported. Chrome supports the “image/webp” type. Specific reference HTMLCanvasElement. ToDataURL

Concrete code implementation is as follows:

(function () {// Canvas implements watermark function __canvasWM({// use ES6's default function to set the default values of parameters // For details, see watermark https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters container = document.body, width = '200px', height = '150px', textAlign = 'center', textBaseline = 'middle', Rotate = 0, rotate = 0, rotate = 0, rotate = 0, rotate = 0, rotate = 0, rotate = 0, rotate = 0 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:! Canvas to achieve web page watermarking effect

To make this method more general and compatible with different reference methods, we could add this code:

// 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 satisfy our needs, but there is a problem: users with a little knowledge of browser usage or web pages can use the browser developer tools to dynamically change the DOM properties or structure. At this point, there are two solutions:

  1. Monitor the changes in the watermark div, record the innerHTML of the newly generated div, take a new value every few seconds, and regenerate the watermark once the change occurs. However, this approach can affect performance;
  2. Using MutationObserver

MutationObserver provides developers with the ability to respond appropriately to changes in the DOM tree within a range.

MutationObserver compatibility

The compatibility table shows that advanced browser and mobile browser support is very good. The Mutation Observer API is used to monitor DOM changes. The API is notified of any changes to the DOM, such as changes in nodes, changes in attributes, and changes in text content. Using the MutationObserver constructor, create a new observer instance with a callback function that takes two arguments, the first being the variable array and the second being the observer instance. The observe method of an instance of the MutationObserver is used to start the listener and takes two arguments. The first argument: the DOM node to be observed. The second argument: a configuration object that specifies the specific changes to be observed.

attribute describe
childList Set to true if you want to look at the children of the target node (a child node has been added or removed).
attributes Set to true if you want to observe the property node of the target node (when a property is added or removed, or when the property value of a property 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 also needs to be observed for changes.
subtree Set to true if, in addition to the target node, you want to observe all descendants of the target node (observe all three of the above node changes in the entire DOM tree that the target node contains).
attributeOldValue With the attributes attribute already set to true, set this to true if you need to record the attribute value before the changed attribute node (in the oldValue attribute of the MutationRecord object below).
characterDataOldValue With the characterData property already set to true, set it to true if the text content before the characterData node changes needs to be recorded (in the oldValue property of the MutationRecord object below).
attributeFilter An array of attribute names (namespaces are not specified). Only changes to the attribute names contained in this array are observed. Changes to attributes with other names are ignored.

MutationObserver can only monitor property changes, adding or deleting child nodes, and so on. There is no way to monitor the parent node when the MutationObserver itself is deleted. Therefore, after final modification, the code is:

(function () {// Canvas implements watermark function __canvasWM({// use ES6's default function to set the default values of parameters // For details, see watermark https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters container = document.body, width = '300px', height = '200px', textAlign = 'center', textBaseline = 'middle', Rotate = 0, rotate = 0, rotate = 0, rotate = 0, rotate = 0, rotate = 0, rotate = 0, rotate = 0 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'); // Recall __canvasWM if ((__wm && __wm.getattribute ('style')) only if __wm element changes! == styleStr) || ! __wm) {// avoid firing mo.disconnect() all the time; 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 watermark through SVG

Scalable Vector Graphics (SVG) is a graphical format based on extensible Markup Language (XML) for describing two-dimensional Vector Graphics. SVG is an open standard developed by the W3C. “– Wikipedia

SVG Browser Compatibility

Compared with Canvas, SVG has better browser compatibility. The watermark generation method of SVG is similar to that of Canvas, but the generation method of base64Url is changed to SVG. The details are as follows:

(function () {// watermark function __svgWM({container = document.body, content = 'do not upload ', 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"); / /... //... }) (); __svgWM({ content: 'QQMusicFE' })Copy the code

Generate watermark with NodeJS

As a modern front-end developer, Node.js also needs to be mastered. We can also generate web page watermarks using NodeJS (or better yet, using the user client for performance reasons). The front end sends a request, the parameter takes the watermark content, the background returns the picture content. Specific implementation (Koa2 environment) :

  1. Install gm and related environments. See the GM documentation for details
  2. ctx.type = 'image/png';Set the response to the image type
  3. The image generation process is asynchronous, so you need to wrap a Promise in order to assign a value to ctx.body in async/await mode
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 it is just a simple watermark display, it is recommended to generate in the browser, the performance is better

Image watermark generation solution

In addition to adding a watermark to the web page, sometimes we need to add a watermark to the picture, so that after the user saves the picture, with the watermark source information, not only can protect the copyright, the watermark of other information can also prevent leaks.

Watermark images using 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)', content = 'Do not send ', 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 a watermark for the image

Use NodeJS to batch watermark images

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 it is just a simple watermark display, it is recommended to generate in the browser, the performance is better

expand

Hidden watermark

Ali some time ago with screenshots to find the moon cake event leaker, in fact, is using a hidden watermark. It’s not really the front end for the most part, but it’s something we should know. 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 the technology on the sly.

Use encrypted watermarked content

The front generated watermark can also, others can also be generated in the same way, there may be “cast the blame” (may be 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 and so on. 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 not reversible, but can be traced through a global traversal of all users. This can prevent watermarking fraud and can trace the information of the real watermark.

MD5 exports. MD5 = function (content) {const salt = function (content) 'microzz_asd! @#IdSDAS~~'; return utils.md5(utils.md5(content + salt)); }Copy the code

conclusion

Security issues can not be careless, for some more sensitive content, we can use the above watermarking scheme by combination, so as to maximize the alert to the viewer, reduce the leak, even if the leak, it is possible to track the leaker.

Refer to the link

  1. Unspeakable secret – The front end can also play steganography

  2. Ruan Yifeng -Mutation Observer API

  3. Lucifer – Based on KM watermark image web page watermark implementation scheme

  4. Damon – Web watermark bright watermark front-end SVG implementation scheme