人机校验滑动组件失效定位及解决方案

问题

有业务人员反馈,公司的Saas平台的登录人机校验的滑动操作无法滑动。表现为:

  • 测试环境公司登录时滑动组件可以滑动。
  • UAT环境A公司可以滑动,B公司无法滑动。
  • https环境不可以滑动,http环境可以滑动。
  • 开发人员在以上相同环境都可以正常操作。
  • 开发人员在任何浏览器版本都可以正常操作。
  • 业务人员的chrome是最新版本。

基于以上情况,我们来的业务同事的电脑前,分别使用触摸板和鼠标操作,确实和业务人员表述的一致。

定位问题

按照经验来看,代码应该是没问题的,可能是业务同事的浏览器插件导致的,于是使用浏览器的隐私模式操作,果然操作正常了。

既然问题的方向找到了,接下来就要找到影响的插件。

分别对比了普通模式下生成的html和隐私模式下生成的袁术有什么不一样。

以下我们以某呼为例,发现滑动组件的滑块的样式被污染了

全局被添加了样式,其中关键字是#allow-copy_script

css 复制代码
html, body, *, *::before, *::after, html body *, #allow-copy_script ~ body * {
  -webkit-user-select: initial !important;
  user-select: initial !important;
}

检查html中被插入了一段css代码段

解释:这段样式的代码的意思是所有的元素都强制设置用户可选中,特别是#allow-copy_script选择器后面的body下的所有元素都应用。但可以去掉这段css,依然不能滑动。

继续在页面上搜索id="allow-coyp_script",发现有一段script,是插件引入的。

使用新的tab页打开chrome-extension://aefehdhdciieocakfobpaaolhipkcpgc/content_scripts/copy.js,就可以看到插件的代码

其中aefehdhdciieocakfobpaaolhipkcpgc是浏览器插件的id,可以通过id访问这个插件的设置页面chrome://extensions/?id=aefehdhdciieocakfobpaaolhipkcpgc

禁用它,然后刷新页面,果然恢复正常了。滑动组件可以正常滑动了。

源码分析

分析下源码,是一个IIFE(立即调用函数),

尝试修改插件源码,chrome提供了Overrides功能,可以在本地映射一个文件夹,修改的源码会保存到这个本地文件夹中,使得可以修改和调试页面js、css、网络请求等。

设置Overrides本地目录

在Sources > Page中找到插件的代码,并点击鼠标右键选择覆盖内容

会自动切换到Overrides选项卡,如果你修改了内容,会有一个带颜色的点,如果保存失败,则会有一个黄色的警告图标,鼠标放上去会告诉你为啥会失败,一般是没有设置本地目录。

尝试修改下代码,在代码前添加拦截添加监听的立即执行函数,并标记下断点,刷新页面就可以进行调试。

下面是我修改的代码,目的是拦截所有注册的事件监听,拦截的事件就是插件添加的那些事件,那滑动组件使用了"mousedown", "mouseup", "mousemove"这些事件,必定会被插件拦截并阻止了事件冒泡。

typescript 复制代码
(function() {
  const originalAddEventListener = EventTarget.prototype.addEventListener;

  EventTarget.prototype.addEventListener = function(type, listener, options) {
    const blockedEvents = [
      "copy", "cut", "contextmenu", "selectstart",
      "mousedown", "mouseup", "mousemove",
      "keydown", "keypress", "keyup"
    ];

    // 插件注册这些事件时拦截掉
    if (
      this === document.documentElement &&
      typeof listener === 'function' &&
      blockedEvents.includes(type) &&
      options && options.capture === true
    ) {
      console.warn("拦截了插件尝试注册的事件监听器:", type);
      return; // 阻止注册
    }

    // 否则放行
    return originalAddEventListener.call(this, type, listener, options);
  };
})();

!function() {
    let e = !1;
    document.addEventListener("allow_copy", (t => {
        e = t.detail.unlock
    }
    ));
    const t = t => {
        e && (t.stopPropagation(),
        t.stopImmediatePropagation && t.stopImmediatePropagation())
    }
    ;
    ["copy", "cut", "contextmenu", "selectstart", "mousedown", "mouseup", "mousemove", "keydown", "keypress", "keyup"].forEach((e => {
        document.documentElement.addEventListener(e, t, {
            capture: !0
        })
    }
    ))
}();

开启浏览器开发者工具-》保证插件是开启的-》修改插件源码并保存-》刷新页面这时候进入断点。并且日志打印拦截成功,滑块也可以正常滑动了。

注意,这里chrome有一个bug,仅每次修改源码的第一次刷新时或者打开开发者工具第一次刷新时,代码才会生效,所以每次刷新前都需要修改一下代码,比如给debugger;debugger删除了增加分号。或者重新打开开发者工具。 但修改网络请求和响应则是每次刷新都会生效。 以上都只在开发者工具打开的情况在生效。

解决方案

  • 拦截allow_copy事件,禁止注册 ⚠️

    html 复制代码
    <script>
        // 阻止插件注册 allow_copy 的监听器
        Object.defineProperty(document, 'addEventListener', {
            configurable: false,
            writable: false,
            value: new Proxy(document.addEventListener,{
                apply(target, thisArg, args) {
                    const [eventName] = args;
                    if (eventName === 'allow_copy') {
                        console.warn('阻止插件监听 allow_copy 事件');
                        return;
                        // 阻止插件注册该事件
                    }
                    return Reflect.apply(target, thisArg, args);
                },
            }),
        });
    </script>

该方法必须保证在插件代码执行前执行,可以添加到你页面代码的<head>中,但如果另一个插件使用了另一个自定义事件名称enable_copy或者其他的呢?所以这种方法不可取。

  • 提醒用户

很多网站在启用了插件后,页面会被删除、添加一些元素,或者一些元素显示不正常,则会给出一个提示,让用户自主选择是否继续访问。

  • 更换人机校验方案

    使用点击校验、图文识别、生物校验等方案来替换滑动校验,但成本稍高、如果设计不当,会导致用户流失,且需要搭建或实现服务或接入第三方服务,既安全又准确,可以考虑,许多大公司都是采用这种方案。

  • iframe嵌入页面,iframe页面不受插件影响

    成本很低,实现快速掘金、豆瓣等都是采用这种方案,在业务同事发现问题的时候,我们发现京东登录时滑块也被插件影响了,今天上去查看京东登录,京东也使用iframe嵌入滑块了。

以上就是定位了分析及解决方案的全过程。

参考文档

立即调用调用函数表达式

本地覆盖网页内容和 HTTP 响应头

使用本地副本替代网页资源 ("替代"选项卡)

相关推荐
暮星3 分钟前
聊聊 JavaScript 的 ASI 机制
前端·javascript
Neon12049 分钟前
JavaScript 文件在页面渲染中的加载机制详解
前端·面试
T兮尔10 分钟前
ios和安卓软键盘问题
前端
Neon120411 分钟前
面试复盘:如何回答「千万级Tree组件」封装问题?
前端·面试
Neon120414 分钟前
面试题:Vue2 中 template 的解析过程详解
前端·面试
光影少年14 分钟前
js防抖、节流和扁平化实现
前端·javascript·掘金·金石计划
理子15 分钟前
tauri2前端获取应用配置目录并创建文件和目录
前端
却尘17 分钟前
React状态的人格分裂:当Vibe Coding遇上状态污染,坑你就完了。
前端·react.js·vibecoding
Neon120419 分钟前
前端方案设计:实现接口缓存
前端·javascript