前端如何防止数据被异常篡改并且复原数据

这篇具有很好参考价值的文章主要介绍了前端如何防止数据被异常篡改并且复原数据。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

每天,我们都在和各种文档打交道,PRD、技术方案、个人笔记等等等。

其实文档排版有很多学问,就像我,对排版有强迫症,见不得英文与中文之间不加空格

所以,最近在做这么一个谷歌扩展插件 chrome-extension-text-formatting,通过谷歌扩展,快速将选中文本,格式化为符合 中文文案排版指北 的文本。

emmm,什么是排版指南?简单来说它的目的在于统一中文文案、排版的相关用法,降低团队成员之间的沟通成本,增强网站气质。

举个例子:

中英文之间需要增加空格

正确:

在 LeanCloud 上,数据存储是围绕 AVObject 进行的。

错误:

在LeanCloud上,数据存储是围绕AVObject进行的。

在 LeanCloud上,数据存储是围绕AVObject 进行的。

完整的正确用法:

在 LeanCloud 上,数据存储是围绕 AVObject 进行的。每个 AVObject 都包含了与 JSON 兼容的 key-value 对应的数据。数据是 schema-free 的,你不需要在每个 AVObject 上提前指定存在哪些键,只要直接设定对应的 key-value 即可。

例外:「豆瓣FM」等产品名词,按照官方所定义的格式书写。

中文与数字之间需要增加空格

正确:

今天出去买菜花了 5000 元。

错误:

今天出去买菜花了 5000元。

今天出去买菜花了5000元。

当然,整个排版规范不仅仅局限于此,上面只是简单列出部分规范内容。而且,这玩意属于建议,很难强迫推广开来。所以,我就想着实现这么一个谷歌插件扩展,一键实现选中文本的格式化。

看个示意图:

前端如何防止数据被异常篡改并且复原数据

适用于各种文本编辑框,当然 Excel 也可以:

前端如何防止数据被异常篡改并且复原数据

当然,这都不是本文的重点

兼容语雀文档遇到的异常场景

因为各个文档平台存在一定的差异性,所以在扩展的制作过程,需要去兼容不同的文档平台(当然,更多的是我自己比较常用的一些文档平台,譬如谷歌文档、语雀、有道云、Github 等等)。

整体来说,整个扩展的功能非常简单,一个极简流程如下:

前端如何防止数据被异常篡改并且复原数据

需要注意的是,上面的操作,大部分都是基于插入到页面的 JavaScript 脚本文件进行执行。

在兼容语雀文档的时候,遇到了这么个有趣的场景。

在上面的第 4 步执行完毕后,在我们对替换后的文本进行任意操作时,譬如重新获焦、重新编辑等,被修改的文本都会被进行替换复原,复原成修改前的状态

什么意思呢?看看下面这张实际的截图:

前端如何防止数据被异常篡改并且复原数据

总结一下,语雀这里这个操作是什么意思呢?

在脚本手动替换掉原选取文件后,当再次获焦文本,修改的内容再会被复原

在一番测试后,我理清了语雀文档的逻辑:

  1. 如果是用户正常输入内容,通过键盘敲入内容,或者正常的复制粘贴,文档可以被正常修改,被保存;
  2. 如果文档内容的修改是通过脚本插入、替换,或者文档内容的修改是通过控制台手动修改 DOM,文档的内容都将会被复原;
  3. 利用脚本对内容进行任意修改后,即便不做任何操作,直接点击保存按钮,文档仍然会被复原为操作前的版本;

Oh,这个功能确实是非常的有意思。它的强悍之处在于,它能够识别出内容的修改是常规正常操作,还是脚本、控制台修改等非常规操作。并且在非常规操作之后,回退到最近一次的正常操作版本

那么,语雀它是如何做到这一点的呢?

由于线上编译混淆后的代码比较难以断点调试,所以我们大胆的猜测一下,如果我们需要去实现一个类似的功能,可能从什么方向入手。

MutationObserver 实现文档内容堆栈存储

首先,我们肯定需要用到 MutationObserver。

MutationObserver 是一个 JavaScript API,用于监视 DOM 的变化。它提供了一种异步观察 DOM 树的能力,并在发生变化时触发回调函数。

我们来构建一个在线文档的最小化场景:

<div id="g-container" contenteditable>
    这是 Web 云文档的一段内容,如果直接编辑,可以编辑成功。如果使用控制台修改,数据将会被恢复。
</div>
#g-container {
    width: 400px;
    padding: 20px;
    line-height: 2;
    border: 2px dashed #999;
}

这里,我们利用 HTML 的 contenteditable 属性,实现了一个可编辑的 DIV 框:

前端如何防止数据被异常篡改并且复原数据

接下来,我们就可以利用 MutationObserver,实现对这个 DOM 元素的监听,实现每当此元素的内容发生改变,就触发 MutationObserver 的事件回调,并且通过一个数组,记录下每一次元素改动的结果。

其大致代码如下:

const targetElement = document.getElementById("g-container");
// 记录初始数据
let cacheInitData = '';

function observeElementChanges(element) {
    const changes = []; // 存储变化的数组
    const targetElementCache = element.innerText;

    // 缓存每次的初始数据
    cacheInitData = targetElementCache;
    
    // 创建 MutationObserver 实例
    const observer = new MutationObserver((mutationsList, observer) => {
        // 检查当前是否存在焦点
        mutationsList.forEach((mutation) => {
            console.log('observer', observer);
            const { type, target, addedNodes, removedNodes } = mutation;
            let realtimeText = "";
            
            const change = {
                type,
                target,
                addedNodes: [...addedNodes],
                removedNodes: [...removedNodes],
                realtimeText,
            };
            
            changes.push(change);
        });
        
        console.log("changes", changes);
    });

    // 配置 MutationObserver
    const config = { childList: true, subtree: true, characterData: true };

    // 开始观察元素的变化
    observer.observe(element, config);
}

observeElementChanges(targetElement);

上面的代码,阅读起来需要一点点时间。但是其本质是非常好理解的,我大致将其核心步骤列举一下:

  1. 创建一个 MutationObserver 实例来观察指定 DOM 元素的变化

  2. 定义一个配置对象 config,用于指定观察的选项。在这个例子中,配置对象中设置了

    1. childList: true 表示观察子节点的变化
    2. subtree: true 表示观察所有后代节点的变化
    3. characterData: true 表示观察节点文本内容的变化
  3. 将变化的信息存储在 changes 数组中

  4. changes 数组中的每个元素记录了一次 DOM 变化的信息。每个变化对象包含以下属性:

    1. type:表示变化的类型,可以是 "attributes"(属性变化)、"characterData"(文本内容变化)或 "childList"(子节点变化)。
    2. target:表示发生变化的目标元素。
    3. addedNodes:一个包含新增节点的数组,表示在变化中添加的节点。
    4. removedNodes:一个包含移除节点的数组,表示在变化中移除的节点。
    5. realtimeText:实时文本内容,可以根据具体需求进行设置。

如此一来,我们尝试编辑 DOM 元素,打开控制台,看看每次 changes 输出了什么内容:

前端如何防止数据被异常篡改并且复原数据

可以发现,每一次当 DIV 内的内容被更新,都会触发一次 MutationObserver 的回调。

我们详细展开数组中的两处进行说明:

前端如何防止数据被异常篡改并且复原数据

其中 type 表示这次触发的是 MutationObserver 配置的 config 中的哪一类变化,命中了 characterData,也就是上面提到的文本内容的变化。而 addedNodesremoveDNodes 都为空,说明没有结构上的变化。

两组数据唯一的变化在于 realtimeText 我们利用了这个值记录了可编辑 DOM 元素内文本值内容。

  • 第一次删除了一个句号 ,所以 realtimeText 文本相比初始文本少了个句号
  • 二次操作删除了一个 字,所以 realtimeText 文本相比初始文本少了 复。

后面的数据依次类推。可以看到,有了这个信息,其实我们相当于能够实现整个 DOM 结构的操作堆栈

在此基础上,我们可以在整个监听之前,在 changes 数组中首先压入最开始未经过任何操作的数据。这也就意味着我们有能力将数据恢复到用户的操作过程中的任意一步

利用特征状态,识别用户是否是手动输入

有了上面的changes 数组,我们相当于有了用户操作的每一步的堆栈信息。

接下的核心就在于我们应该如何去运用它们

在语雀这个例子中,它的核心点在于:

它能够识别出内容的修改是常规正常操作,还是脚本、控制台修改等非常规操作。并且在非常规操作之后,回退到最近一次的正常操作版本

因此,我们接下来探索的问题就变成了如何识别一个可输入编辑框,它的内容修改是正常输入修改,还是非正常输入修改。

譬如,思考一下,当用户正常输入或者复制粘贴内容到编辑框,应该会有什么特征信息:

  1. 可以通过 document.activeElement 拿到当前页面获焦的元素,因此可以在每次触发 Mutation 变化的时,多存储一份当前的获焦元素信息,对比内容被修改时的页面获焦元素是否是当前输入框
  2. 尝试判断输入框的获焦状态,可以通过监听 foucsblur 获焦及失焦等事件进行判断
  3. 用户当文本内容改变时,是否有经过触发过键盘事件,譬如 keydown 事件
  4. 用户当文本内容改变时,是否有经过触发过键盘事件的粘贴 paste 事件
  5. 对于直接修改控制台,则可能是除了文本内容外,有 DOM 子树的其他变化,也就是会触发 Mutation 的 childList 变化事件

有了上面的思路,下面我们尝试一下,为了尽可能让 DEMO 好理解,我们稍微简化需求,实现:

  1. 一个输入框,用户正常输入可以改变内容
  2. 当输入框内容通过控制台进行修改,则当元素再次获焦时,恢复到最近一次的手动修改记录
  3. 如果(2)找不到最近一次的手动修改记录,将数据恢复到初始状态

基于此,下面我给出大致的伪代码:

<div id="g-container" contenteditable>这是 Web 云文档的一段内容,如果直接编辑,可以编辑成功。如果使用控制台修改,数据将会被恢复。</div>
const targetElement = document.getElementById("g-container");
// 记录初始数据
let cacheInitData = '';
// 数据复位标志位
let data_fixed_flag = false; 
// 复位缓存对象
let cacheObservingObject = null;
let cacheContainer = null;
let cacheData = '';

function eventBind() {
    targetElement.addEventListener('focus', (e) => {        
        if (data_fixed_flag) {
            cacheContainer.innerText = cacheData;
            cacheObservingObject.disconnect();
            observeElementChanges(targetElement);
            
            data_fixed_flag = false;
        }
    });
}

function observeElementChanges(element) {
    const changes = []; // 存储变化的数组
    const targetElementCache = element.innerText;

    // 缓存每次的初始数据
    cacheInitData = targetElementCache;
    
    // 创建 MutationObserver 实例
    const observer = new MutationObserver((mutationsList, observer) => {
        mutationsList.forEach((mutation) => {
            // console.log('observer', observer);
            const { type, target, addedNodes, removedNodes } = mutation;
            let realtimeText = "";
            
            if (type === "characterData") {
                realtimeText = target.data;
            }
            
            const change = {
                type,
                target,
                addedNodes: [...addedNodes],
                removedNodes: [...removedNodes],
                realtimeText,
                activeElement: document.activeElement
            };
            changes.push(change);
        });
        
        let isFixed = false;
        let container = null;
        
        for (let i = changes.length - 1; i >= 0; i--) {
            const item = changes[i];
            // console.log('i', i);
            if (item.activeElement === element) {
                if (isFixed) {
                    cacheData = item.realtimeText;
                }
                break;
            } else {
                if (!isFixed) {
                    isFixed = true;
                    container = item.target.nodeType === 3 ? item.target.parentElement : item.target;
                    cacheContainer = container;
                    data_fixed_flag = true;
                }
            }
        }
        
        if (data_fixed_flag && cacheData === '') {
            cacheData = cacheInitData;
        }
        
        cacheObservingObject = observer;
    });

    // 配置 MutationObserver
    const config = { childList: true, subtree: true, characterData: true };

    // 开始观察元素的变化
    observer.observe(element, config);
    eventBind();
    
    // 返回停止观察并返回变化数组的函数
    return () => {
        observer.disconnect();
        return changes;
    };
}

observeElementChanges(targetElement);

简单解释一下,大致流程如下

  1. observeElementChanges 上文已经出现过,核心在于记录每一次 DOM 元素的变化,将变化内容记录在 changes 数组中

    1. 多记录了一个 activeElement,表示每次 DOM 元素发生变化时,页面的焦点元素
  2. 每次 changes 更新后,倒序遍历一次 changes 数组

    1. 如果当前页面获焦元素与当前发生变化的 DOM 元素不是同一个元素,则认为是一次非法修改,记录两个标志位 isFixeddata_fixed_flag,此时继续向前寻找最近一次正常修改记录
    2. isFixed 用于向前寻找最近一次正常修改记录后,将最近一次修改的堆栈信息进行保存
  3. data_fixed_flag 标志位用于当元素被再次获焦时(触发 focus 事件),根据标志位判断是否需要回滚恢复数据

OK,此时,我们来看看整体效果:

前端如何防止数据被异常篡改并且复原数据

这样,我们就成功的实现了识别非正常操作,并且恢复到上一次正常数据。

当然,实际场景肯定比这个复杂,并且需要考虑更多的细节,这里为了整体的可理解性,简化了整个 DEMO 的表达。

完整的 DEMO 效果,你可以戳这里体验:[CodePen Demo -- Editable Text Fixed]

一些思考

至于这个功能有什么用?这个就见仁见智了,至少对于开发扩展插件的我而言,是一个非常棘手的问题,当然从语雀的角度而言,更多也许是从安全方面进行考量的。

当然,我们不应该局限于这个场景,思考一下,这个方案其实可以应用在非常多其它场景,举个例子:

  1. 前端页面水印,实现当水印 DOM 的样式、结构、或者内容被篡改时,立即进行水印恢复

当然,破解起来也有一些方式,对于扩展插件而言,我可以通过更早的向页面注入我的 content script,在页面加载渲染前,对全局的 MutationObserver 对象进行劫持。

总而言之,可以通过本文提供的思路,尝试进行更多有意思的前端交互限制。

最后

好了,本文到此结束,希望对你有帮助 😃

想 Get 到最有意思的 CSS 资讯,千万不要错过我的公众号 -- iCSS前端趣闻 😄

更多精彩 CSS 技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。

如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。文章来源地址https://www.toymoban.com/news/detail-746009.html

到了这里,关于前端如何防止数据被异常篡改并且复原数据的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 中间件中防止数据上下文并发导致异常

    在ASP.NET Core中,如果你想在中间件中只使用一个实例的数据库上下文(DbContext),你需要确保这个上下文在整个请求中是可用的,并且中间件在处理请求时能够访问它。以下是如何做到这一点的步骤: 注册DbContext为Scoped : 在Startup.cs的 ConfigureServices 方法中,你需要将你的D

    2024年01月24日
    浏览(33)
  • 前端安全系列(一):如何防止XSS攻击?

    随着互联网的高速发展,信息安全问题已经成为企业最为关注的焦点之一,而前端又是引发企业安全问题的高危据点。在移动互联网时代,前端人员除了传统的 XSS、CSRF 等安全问题之外,又时常遭遇网络劫持、非法调用 Hybrid API 等新型安全问题。当然,浏览器自身也在不断在

    2024年02月02日
    浏览(56)
  • 前端如何实现隐藏滚动条,并且页面还可以滚动

    在前端中,可以通过 CSS 和一些简单的样式调整来实现隐藏滚动条,同时保持页面可滚动。这通常涉及到在容器内部创建滚动区域,并隐藏默认的滚动条样式。 下面是实现这一效果的基本步骤: 创建一个滚动容器元素,使其包裹需要滚动的内容。 通过 CSS 隐藏默认的滚动条样

    2024年02月02日
    浏览(42)
  • 区块链溯源的技术实现:如何确保数据的不可篡改性?

    区块链技术是一种分布式、去中心化的数字账本技术,它可以确保数据的完整性、不可篡改性和透明度。在现实生活中,溯源问题是一种常见的问题,例如食品安全、药品来源等。通过区块链技术,我们可以确保数据的不可篡改性,从而提高溯源的可靠性。 在这篇文章中,我

    2024年04月10日
    浏览(34)
  • 前端 img图片如何 展示 base64 格式(并且下载到本地)

    如题:最近在做项目发现页面上有些图片是动态获取的,也就是后台给我们返回图片的存放地址,一般都是放在服务器上的某个位置,我们直接拿到渲染一下就行了,(前提是不存在跨域问题), 但是由于项目特殊性,后台使用了Python 渲染出来的图片是svg格式的图片,并且

    2024年02月09日
    浏览(43)
  • 前端AES加密,后端解密,有效防止数据外泄

    在工作中经常遇到密码明文传输这个问题,为了让密码安全些会让加密,现在有个比较方便的AES加密(前端密钥可能存在泄露风险,应该放到配置项中): 一、前端加密 1、首先引入前端需要用到的js:crypto-js,下载地址: CryptoJS-v4.1.1 https://www.aliyundrive.com/s/bXP6M8ZxVAD 点击链接

    2024年02月16日
    浏览(34)
  • 前端开发调试技巧:如何在Component下选中当前插件并且查看当前插件信息

    在react开发项目中,在Component下选中组件,然后在控制台输$r 按回车键即可输出该组件信息。例如 $r.props输出该组件的props参数。例子详情见如下截图

    2024年02月07日
    浏览(41)
  • 【VUE】前端实现防篡改的水印

    图片加水印的操作一般是由后端来完成,有些站点保护的知识产权的类型可能比较多,不仅仅是图片,可能还有视频、文字等等,对于不同类型的对象添加水印后端操作比较复杂,所有有些站点逐步的让前端去进行水印添加的操作。 如果用 React 框架来进行开发就比较简单,

    2024年02月14日
    浏览(28)
  • 区块链的去中心化账本有和意义?为什么要哈希运算?如何保证数据不可篡改?

    1、中心化账本是什么? 在互联网的世界里,价值是用数字来呈现的。而数字文件是可以无限复制的,在互联网的世界数字具有易错、易改、易拷贝的特性,但是价值是不能复制的。为了解决这一难题发展为由一个中心化的机构(支付宝/微信支付/银联等)负责记账和记录账户余

    2023年04月23日
    浏览(46)
  • 企业该如何防止数据泄漏问题

    根据Verizon《2022 数据泄露调查报告》显示,2022年数据泄露事件中82%的违规行为涉及人为因素,勒索软件泄露事件增加了13%,超过过去五年的总和,数据安全已变成关乎国家安全与社会经济快速发展的重大问题。随着企业的文档数据逐渐增多,为了避免数据泄漏事件,企业可以

    2024年02月12日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包