JS检测,禁用浏览器开发者工具

快捷键阻止打开浏览器开发者工具

在开发网页时,有时需要防止用户打开浏览器的开发者工具。首先,我们要了解开发者工具的常见打开方式:

  • F12
  • Ctrl + Shift + I
  • Ctrl + Shift + C
  • Shift + F10(打开右键菜单)

为了避免这些快捷键,我们可以通过监听 keydown 事件,拦截这些快捷键并禁止默认操作。以下是实现的 JavaScript 代码:

document.addEventListener('keydown', (e) => {
    const code = e.code;
    const ctrl = e.ctrlKey;
    const shift = e.shiftKey;
    const isCSI = ctrl && shift && code === 'KeyI';
    const isF12 = code === 'F12';
    const isCSC = ctrl && shift && code === 'KeyC';
    const isSF10 = shift && code === 'F10';

    if (isF12 || isCSI || isCSC || isSF10) {
        e.preventDefault(); // 禁止默认行为
    }
});

禁用右键菜单

另外,右键菜单也是用户可能用来调试的工具。我们可以通过拦截 contextmenu 事件来禁用右键菜单:

document.addEventListener('contextmenu', (e) => {
    e.preventDefault(); // 禁止右键菜单
});

无法阻止的情况

虽然以上方法能有效地防止开发者工具被打开,但浏览器设置(例如从“更多工具”中选择开发者工具)仍然无法被阻止。此时,如果打开了开发者工具,可以通过一些方法来检测和关闭它。

简单的开发者工具检测方法

其中,最简单的一种做法是监听浏览器窗口的大小变化,通过判断当前的可视区域与浏览器窗口的总宽高差异来判断是否打开了开发者工具。因为当开发者工具打开时,浏览器的可视区域通常会缩小,从而引发窗口大小变化。

JS检测,禁用浏览器开发者工具-第1张图片-IT技术视界下面是实现的 JavaScript 代码:

window.addEventListener('resize', function () {
    // outerWidth 为整个浏览器的宽度,innerWidth 为可视区域的宽度
    // 如果两者有差距,说明开发者工具可能已打开
    if (window.outerWidth - window.innerWidth > 0 || window.outerHeight - window.innerHeight > 130) {
        console.log("开发者工具已打开!");
        // TODO: 做一些操作,例如重定向,重写innerHTML,关闭页面等等
        location.href = "about:blank"; // 例如跳转到空白页
    }
});

这种方式依赖于窗口大小的变化来推测开发者工具的状态,但它也有局限性。比如,当开发者工具被分离成一个独立窗口时,浏览器的可视区域并不会发生变化,因此此方法无法检测到这种情况。此外,如果用户在先打开一个正常页面后,再访问禁止打开开发者工具的网站,依然无法触发检测。

使用性能检测判断是否开启开发者工具

另一种方法是通过性能检测来判断是否打开了开发者工具。我们可以通过控制台打印大对象数组,并检查打印时的性能差异。打开开发者工具时,控制台会展开对象,导致性能差异增大,从而判断是否开启了开发者工具:

function now() {
    return new Date().getTime();
}

function createLargeObject() {
    const largeObject = {};
    for (let i = 0; i < 500; i++) {
        largeObject[`${i}`] = `${i}`;
    }
    return largeObject;
}

function createLargeObjectArray() {
    const largeObject = createLargeObject();
    const largeObjectArray = [];
    for (let i = 0; i < 50; i++) {
        largeObjectArray.push(largeObject);
    }
    return largeObjectArray;
}

function calculateTime(func) {
    const start = now();
    func();
    return now() - start;
}

const largeObjectArray = createLargeObjectArray();
let maxPrintTime = 0;

setInterval(() => {
    const tablePrintTime = calculateTime(() => { console.table(largeObjectArray); });
    const printLogTime = calculateTime(() => { console.log(largeObjectArray); });
    maxPrintTime = Math.max(maxPrintTime, printLogTime);

    if (tablePrintTime === 0 || maxPrintTime === 0) {
        return;
    } else {
        if (tablePrintTime > maxPrintTime * 10) {
            console.log("时间对比", tablePrintTime, maxPrintTime);
            window.close(); // 关闭页面
        }
    }
}, 500);

通过 debugger 判断是否开启开发者工具

我们还可以通过 debugger 语句来间接判断开发者工具是否已开启。其原理是通过触发断点计算时间差来判断:

function now() {
    return new Date().getTime();
}

const debuggerChecker = {
    name: 'debugger-checker',
    async isOpen() {
        const startTime = now();
        (function () {}).constructor('debugger')();
        return now() - startTime > 100;
    },
    async isEnable(){
        return true;
    },
};

async function _detectLoop() {
    let isOpen = false;
    isOpen = await debuggerChecker.isOpen();

    if (isOpen) {
        console.log('开发者工具已打开');
        window.close(); // 关闭页面
    }

    setTimeout(_detectLoop, 500);
}

window._checkers = [debuggerChecker];
window._isOpen = false;
_detectLoop();

监视 DOM 修改

除了通过窗口大小变化来检测开发者工具的打开与否外,我们还可以监视页面的 DOM 变化。这样一旦页面的任何元素被修改,就会重新加载数据,阻止用户对页面元素的篡改(此方法不支持 IE9 以下浏览器)。

以下是实现的 JavaScript 代码:

if (window.addEventListener) {
    window.addEventListener("DOMCharacterDataModified", function () {
        window.location.reload(); // 重新加载页面
    }, true);
    window.addEventListener("DOMAttributeNameChanged", function () {
        window.location.reload(); // 重新加载页面
    }, true);
    window.addEventListener("DOMElementNameChanged", function () {
        window.location.reload(); // 重新加载页面
    }, true);
    window.addEventListener("DOMNodeInserted", function () {
        window.location.reload(); // 重新加载页面
    }, true);
    window.addEventListener("DOMNodeInsertedIntoDocument", function () {
        window.location.reload(); // 重新加载页面
    }, true);
    window.addEventListener("DOMNodeRemoved", function () {
        window.location.reload(); // 重新加载页面
    }, true);
    window.addEventListener("DOMNodeRemovedFromDocument", function () {
        window.location.reload(); // 重新加载页面
    }, true);
    window.addEventListener("DOMSubtreeModified", function () {
        window.location.reload(); // 重新加载页面
    }, true);
}

破解方法:

如果不修改 DOM 页面元素,就不会触发这些事件,也就不会导致页面的重新加载。

这种方法可以有效防止通过开发者工具直接修改页面内容,阻止了篡改,但它也有一定的局限性:只要没有进行 DOM 元素的修改,页面就可以继续运行,不会受到干扰。

重写 console 方法

一种防止 console 被用来检测的方式是重写 console 对象的方法,使其失效:

Object.defineProperty(window, 'console', {
    value: {
        table: () => {},
        log: () => {},
        clear: () => {},
        debug: () => {},
        warn: () => {}
    }
});

使用帧劫持技术

帧劫持是一种通过不断重绘页面的方式,来干扰开发者工具的使用。这种技术通常通过周期性地检测未捕获的错误,进而触发页面重载。开发者工具(如 Chrome 或 Firefox)通常会在打开时暴露一些特定的控制台对象(如 console.firebugconsole.exception),因此通过检查这些对象是否存在,可以间接检测开发者工具是否开启。

以下是帧劫持的示例代码:

setInterval(function() {
    if (window.console && (console.firebug || console.exception)) {
        window.location.reload(); // 如果检测到开发者工具已开启,则刷新页面
    }
}, 1000);

 这段代码每隔一秒检测一次,检查控制台是否存在 firebugexception 属性。如果检测到这些属性(通常是开发者工具开启时才会出现),就会重载页面。这种方法通过不断刷新页面,强迫开发者工具使用者不断面对被重载的页面,从而提高了反调试的难度。

检测调试器

另一种常见的反调试策略是检测调试器是否被启用。大多数现代浏览器都提供了调试功能,可以在页面上运行调试器。如果检测到调试器开启,可能会采取一些措施来干扰开发者工具的使用。

以下是检测调试器的示例代码:

(function() {
    const element = new Image();

    Object.defineProperty(element, 'id', {
        get: function() {
            throw new Error('DevTools detected'); // 如果访问 id 属性时触发错误,表示调试器已启用
        }
    });

    try {
        console.log(element); // 尝试打印图像对象
    } catch (e) {
        window.location.reload(); // 如果抛出错误,说明调试器被启用了,重载页面
    }
})();

 这段代码通过在 Image 对象的 id 属性上设置 getter 方法,故意抛出一个错误来检测调试器的开启状态。当开发者工具处于调试模式时,控制台通常会捕捉到这种异常,并显示错误信息。捕获错误后,页面会被重新加载。这种方法利用了浏览器的错误处理机制,通过故意抛出错误来探测调试器的状态,并采取重载页面的方式干扰开发者工具的使用。

结论

目前,前端并没有提供一种专门的 API 来监听开发者工具的打开与关闭。大多数方法都依赖于奇巧技巧,且各有局限性。不过,通过这些方法,我们可以尽可能地防止或检测开发者工具的使用。

THE END