Схема создания водяных знаков внешнего интерфейса (водяной знак веб-страницы + водяной знак изображения)

Node.js JavaScript SVG Canvas

Схема генерации водяных знаков во внешнем интерфейсе

Некоторое время назад я проводил системный аудит в фоновом режиме, и возникла ситуация, когда аудиторы сделали скриншоты и слили содержимое.Хотя содержание скриншотов не является особо важным, нельзя игнорировать вопросы безопасности. Поэтому на системную страницу добавляется водяной знак, который оказывает определенное подсказочное действие на чувствительные операции, такие как скриншоты рецензентов.

Решение для создания веб-водяных знаков

Создание водяного знака через холст

Совместимость с холстом

图片
Здесь мы используем холст для генерации изображений base64 и проверяем совместимость через веб-сайт CanIUse.Если он используется на мобильном терминале и некоторых системах управления, проблема совместимости может быть полностью проигнорирована.

HTMLCanvasElement.toDataURLМетод возвращает URI данных, содержащий изображение. Тип можно использовать с параметром type, который по умолчанию имеет формат PNG. Разрешение картинки 96dpi.

Если высота или ширина холста равна 0, возвращается строка «данные:».
Если входящий тип не "image/png", но возвращаемое значение начинается с "data:image/png", то входящий тип не поддерживается.
Chrome поддерживает тип «image/webp». конкретная ссылкаHTMLCanvasElement.toDataURL

Конкретный код реализован следующим образом:

 (function () {
      // canvas 实现 watermark
      function __canvasWM({
        // 使用 ES6 的函数默认值方式设置参数的默认取值
        // 具体参见 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 = '请勿外传',
        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;
    })();

    // 调用
    __canvasWM({
      content: 'QQMusicFE'
    })

Эффект следующий:
![Canvas реализует эффект водяного знака веб-страницы]

图片

Чтобы сделать этот метод более общим и совместимым с различными методами ссылок, мы также можем добавить этот код:

      // 为了兼容不同的环境
      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;
      }

Кажется, что это удовлетворяет наши потребности, но все еще остается проблема: пользователи, которые немного разбираются в использовании браузеров или веб-страниц, могут использовать инструменты разработчика браузера для динамического изменения свойств или структуры модели DOM и ее удаления. На данный момент есть два решения:

  1. Отслеживайте изменение водяного знака div, записывайте innerHTML только что созданного div, принимайте новое значение каждые несколько секунд и регенерируйте водяной знак после его изменения. Но это может повлиять на производительность;
  2. использоватьMutationObserver

MutationObserver предоставляет разработчикам возможность адекватно реагировать на изменения в дереве DOM в определенной области.

Совместимость с MutationObserver

图片

Таблица совместимости показывает, что поддержка расширенных браузеров и мобильных браузеров очень хорошая.
API Mutation Observer используется для отслеживания изменений DOM. Этот API может уведомлять о любых изменениях в DOM, таких как увеличение или уменьшение узлов, изменения атрибутов и изменения текстового содержимого.
Используйте конструктор MutationObserver для создания нового экземпляра наблюдателя с функцией обратного вызова, которая принимает два параметра: первый — массив мутаций, а второй — экземпляр наблюдателя. Метод наблюдения экземпляра MutationObserver используется для запуска слушателя и принимает два параметра.
Первый аргумент: следует соблюдать NOD NODE, второй параметр: объект конфигурации, укажите конкретное изменение, которое необходимо наблюдать, следующие:

Атрибуты описывать
childList Установите значение true, если вам нужно наблюдать за дочерними узлами целевого узла (дочерний узел был добавлен или дочерний узел был удален).
attributes Установите значение true, если вам необходимо наблюдать за узлом атрибута целевого узла (атрибут был добавлен или удален, а значение атрибута изменилось).
characterData Если целевой узел является узлом characterData (абстрактный интерфейс, который может быть текстовым узлом, узлом комментариев и узлом инструкций по обработке), также необходимо наблюдать, изменяется ли текстовое содержимое узла, а затем устанавливается значение true. .
subtree В дополнение к целевому узлу, если вам также необходимо наблюдать за всеми узлами-потомками целевого узла (наблюдайте за тремя вышеуказанными изменениями узлов во всем дереве DOM, содержащемся в целевом узле), установите значение true.
attributeOldValue В предположении, что для атрибута атрибутов установлено значение true, если значение атрибута перед измененным узлом атрибута необходимо записать (записать в атрибут oldValue объекта MutationRecord ниже), установите значение true.
characterDataOldValue Под предпосылкой свойства HASTARCHDATA уже установлено значение true, если вам необходимо записать контент текста до измененного узла HATCLEDATA (записанный в свойство OldValue следующего объекта MutationRecord), установите значение true.
attributeFilter Массив имен свойств (не нужно указывать пространство имен), который будет наблюдаться только при изменении имен свойств, содержащихся в массиве, а свойства других имен будут игнорироваться при их изменении.

MutationObserverОн может только отслеживать такие атрибуты, как изменения атрибутов, добавление и удаление дочерних узлов и т. д. Сам себя удалить невозможно, и требования могут быть выполнены путем наблюдения за родительским узлом. Таким образом, код после окончательного преобразования:

   (function () {
      // canvas 实现 watermark
      function __canvasWM({
        // 使用 ES6 的函数默认值方式设置参数的默认取值
        // 具体参见 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 = '请勿外传',
        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');
            // 只在__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
          })
        }

      }

      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;
      }
    })();

    // 调用
    __canvasWM({
      content: 'QQMusicFE'
    });

Создать водяной знак через SVG

SVG: масштабируемая векторная графика (англ. Scalable Vector Graphics, SVG) — это графический формат, основанный на расширяемом языке разметки (XML) для описания двумерной векторной графики. SVG был разработан W3C и является открытым стандартом. --Википедия

Совместимость с браузером SVG

图片

По сравнению с Canvas, SVG лучше совместим с браузерами Способ использования SVG для создания водяных знаков подобен способу Canvas, но способ создания base64Url изменен на SVG. детали следующим образом:

     (function () {
      // svg 实现 watermark
      function __svgWM({
        container = document.body,
        content = '请勿外传',
        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");
     // ...
     // 与 canvas 的一致
     // ...
    })();

    __svgWM({
      content: 'QQMusicFE'
    })

Создание водяного знака через NodeJS

Как современному фронтенд-разработчику, Node.JS также необходимо освоить. Мы также можем генерировать водяные знаки веб-страниц через NodeJS (лучший способ — использовать пользовательский клиент для их создания из соображений производительности). Внешний интерфейс отправляет запрос, параметры помечаются водяными знаками, а фон возвращает содержимое изображения.
Конкретная реализация (среда Koa2):

  1. Установите gm и сопутствующую среду, подробности см.гм документация
  2. ctx.type = 'image/png';установить ответ как тип изображения
  3. Процесс генерации изображения асинхронный, поэтому нужно обернуть слой Promise, чтобы можно было присваивать значения ctx.body через 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(255,255,255,0)")
        .fontSize(40)
        .drawText(10, 50, text)
        .write(require('path').join(__dirname, `./${text}.png`), function (err) {
          if (err) {
            reject(err);
          } else {
            resolve(fs.readFileSync(require('path').join(__dirname, `./${text}.png`)))
          }
        });
    })
  })());
});

Если это просто отображение водяного знака, рекомендуется создать его в браузере, производительность выше.

Решение для создания водяных знаков изображения

В дополнение к добавлению водяных знаков на веб-страницы иногда нам необходимо добавлять водяные знаки к изображениям, чтобы после того, как пользователь сохранил изображение, была включена исходная информация водяного знака, которая может не только защитить авторские права, но и предотвратить другую информацию. водяной знак от утечки.

Водяной знак изображения через холст

Реализация выглядит следующим образом:

    (function() {
      function __picWM({
        url = '',
        textAlign = 'center',
        textBaseline = 'middle',
        font = "20px Microsoft Yahei",
        fillStyle = 'rgba(184, 184, 184, 0.8)',
        content = '请勿外传',
        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;
      }
      
    })();

    // 调用
    __picWM({
        url: 'http://localhost:3000/imgs/google.png',
        content: 'QQMusicFE',
        cb: (base64Url) => {
          document.querySelector('img').src = base64Url
        },
      });

Эффект следующий:

Изображения водяных знаков на холсте

图片

Пакетная обработка изображений водяных знаков с помощью NodeJS

Мы также можем добавлять водяные знаки на изображения через библиотеку gm.

function picWM(path, text) {
  imageMagick(path)
    .drawText(10, 50, text)
    .write(require('path').join(__dirname, `./${text}.png`), function (err) {
      if (err) {
        console.log(err);
      }
    });
}

Если вам нужно группировать изображения, просто просмотрите соответствующие файлы.

Если это просто отображение водяного знака, рекомендуется создать его в браузере, производительность выше.

расширять

скрытый водяной знак

Краткое изложение Али, Али, я нашел след событий лунного торта, на самом деле, он был скрыт в воде. Это на самом деле большая степень, но мы также должны понимать. Команда Alloyteam написала одинСекрет, который нельзя раскрыть — стеганография изображений, которую также можно воспроизвести во внешнем интерфейсе., добавление "скрытого водяного знака" на картинку через Canvas, для картинок, сохраненных пользователем, легко восстановить скрытый контент, но для скриншотов или обработанных фото бессильно, а вот для отображения некоторых конфиденциальных файлов картинок - возможность использовать технологию тайно.

Используйте зашифрованный водяной знак

Водяной знак, генерируемый интерфейсом, также может быть сгенерирован, и другие также могут генерировать его таким же образом.Может быть «обвинять других» (может быть, это слишком много размышлений), и нам все еще нужно более безопасное решение. Содержимое водяного знака может содержать различную закодированную информацию, включая имя пользователя, идентификатор пользователя, время и так далее. Например, если мы просто хотим сохранить уникальный идентификатор пользователя, нам нужно передать идентификатор пользователя в метод md5 ниже, чтобы сгенерировать уникальный идентификатор. Закодированная информация необратима, но ее можно отследить путем обхода всех пользователей по всему миру. Таким образом можно предотвратить мошенничество с водяными знаками и отследить информацию о реальном водяном знаке.

// MD5加密库 utility
const utils = require('utility')

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

Суммировать

Вопросы безопасности не должны быть небрежными.Для некоторого конфиденциального контента мы можем использовать вышеуказанные схемы водяных знаков в сочетании, чтобы максимизировать эффект предупреждения для браузера и уменьшить утечку секретов.Даже если секрет просочился, можно отследить ликера.

Ссылка на ссылку

  1. Секрет, который нельзя раскрыть — стеганография изображений, которую также можно воспроизвести во внешнем интерфейсе.

  2. Yifeng Ruan — API наблюдения за мутациями

  3. lucifer-реализация схемы водяных знаков изображения веб-страницы на основе водяных знаков KM

  4. damon-Webpage водяные знаки и схема реализации SVG переднего плана