JS檢測瀏覽器開發(fā)者工具是否打開的方法詳解
在某些情況下我們需要檢測當(dāng)前用戶是否打開了瀏覽器開發(fā)者工具,比如前端爬蟲檢測,如果檢測到用戶打開了控制臺就認(rèn)為是潛在的爬蟲用戶,再通過其它策略對其進(jìn)行處理。本篇文章主要講述幾種前端JS檢測開發(fā)者工具是否打開的方法。
一、重寫toString()
對于一些瀏覽器,比如Chrome、FireFox,如果控制臺輸出的是對象,則保留對象的引用,每次打開開發(fā)者工具的時候都會重新調(diào)用一下對象的toString()方法將返回結(jié)果打印到控制臺(console tab)上。
所以只需要創(chuàng)建一個對象,重寫它的toString()方法,然后在頁面初始化的時候就將其打印在控制臺上(這里假設(shè)控制臺還沒有打開),當(dāng)用戶打開控制臺時會再去調(diào)用一下這個對象的toString()方法,用戶打開控制臺的行為就會被捕獲到。
下面是一個小小的例子,當(dāng)Chrome用戶的開發(fā)者工具狀態(tài)從關(guān)閉向打開轉(zhuǎn)移時,這個動作會被捕獲到并交由回調(diào)函數(shù)處理:
<html>
<head>
<title>console detect test</title>
</head>
<body>
<script>
/**
* 控制臺打開的時候回調(diào)方法
*/
function consoleOpenCallback(){
alert("CONSOLE OPEN");
return "";
}
/**
* 立即運行函數(shù),用來檢測控制臺是否打開
*/
!function () {
// 創(chuàng)建一個對象
let foo = /./;
// 將其打印到控制臺上,實際上是一個指針
console.log(foo);
// 要在第一次打印完之后再重寫toString方法
foo.toString = consoleOpenCallback;
}()
</script>
</body>
</html>
效果:

當(dāng)?shù)谝淮卧诖隧撁娲蜷_控制臺時會觸發(fā)到檢測,但是如果是在一個已經(jīng)打開了控制臺的窗口中粘貼網(wǎng)址訪問則不會觸發(fā),同理在此頁面上已經(jīng)打開控制臺時刷新也不會觸發(fā)。
這種方式雖然比較取巧,但是并不具有通用性,并且只能捕獲到開發(fā)者工具處于關(guān)閉狀態(tài)向打開狀態(tài)轉(zhuǎn)移的過程,具有一定的局限性。
二、debugger
類似于代碼里的斷點,瀏覽器在打開開發(fā)者工具時(對應(yīng)于代碼調(diào)試時的debug模式)檢測到debugger標(biāo)簽(相當(dāng)于是程序中的斷點)的時候會暫停程序的執(zhí)行:
![]()
此時需要點一下那個藍(lán)色的“Resume script execution”程序才會繼續(xù)執(zhí)行,這中間會有一定的時間差,通過判斷這個時間差大于一定的值就認(rèn)為是打開了開發(fā)者工具。這個方法并不會誤傷,當(dāng)沒有打開開發(fā)者工具時遇到debugger標(biāo)簽不會暫停,所以這種方法還是蠻好的,而且通用性比較廣。
下面是一個使用debugger標(biāo)簽檢測開發(fā)者工具是否打開的例子:
<html>
<head></head>
<body>
<script>
function consoleOpenCallback() {
alert("CONSOLE OPEN");
}
!function () {
const handler = setInterval(() => {
const before = new Date();
debugger;
const after = new Date();
const cost = after.getTime() - before.getTime();
if (cost > 100) {
consoleOpenCallback();
clearInterval(handler)
}
}, 1000)
}();
</script>
</body>
</html>
效果:

但是上面的代碼有一個很嚴(yán)重的bug,就是在執(zhí)行到debugger那一行的時候如果用戶發(fā)現(xiàn)了貓膩沒有點按resume script execution按鈕,而是直接退出頁面的話,那么將不能檢測到本次的打開開發(fā)者工具行為,實際結(jié)果與預(yù)期不符,我認(rèn)為這是嚴(yán)重bug,就像電影里演的不小心踩到地雷及時察覺不抬腳就還有活命機會,到了debugger標(biāo)簽我察覺到這是檢測控制臺是否打開的代碼我退出然后使用其它手段繞過它,那我可能做了一個假功能。
有一個需要注意的地方就是使用此方法的時候當(dāng)卡在debugger標(biāo)簽的時候,用戶是能夠看到debugger標(biāo)簽附近的代碼的,如果是有經(jīng)驗的用戶一眼就能看出里面的道道,所以要想辦法隱藏一下真實目的,比如將debugger標(biāo)簽隱藏,并且對代碼進(jìn)行混淆盡量增加閱讀難度,關(guān)于如何隱藏debugger標(biāo)簽前后的邏輯,可以參考這幾個網(wǎng)站:(當(dāng)前2018-7-4 23:12:17有效)
http://app2.sfda.gov.cn/datasearchp/gzcxSearch.do?formRender=cx&optionType=V1
使用此種方案的話可能有個需要注意的點就是debugger是有可能不會暫停的,比如Chrome瀏覽器的source面板可以選擇在debugger語句時不暫停:

如果這個按鈕被點亮,再測試上面的網(wǎng)頁就會發(fā)現(xiàn)很悲劇檢測代碼失效了,因為debugger標(biāo)簽根本就沒有暫停。
其實debugger標(biāo)簽還有另一種妙用,比如用來反調(diào)試,可以設(shè)定一個每秒就觸發(fā)一個debugger,讓調(diào)試者疲于應(yīng)付debugger或者耗費他額外的成本去覆蓋掉JS,上面給出的幾個網(wǎng)站就是這么做的。
下面是一個使用debugger標(biāo)簽反js調(diào)試的簡單例子:
<html>
<head>
<title>Anti debug</title>
</head>
<body>
<script>
!function () {
setInterval(() => {
debugger;
}, 1000);
}();
</script>
</body>
</html>
效果:

一個實際的例子,這個網(wǎng)站:http://jxw.uou0.com/的js檢測腳本,而針對不同的情況它又會有不同的反調(diào)試策略。
注意要想復(fù)現(xiàn)需要粘貼視頻地址解析之后才會加載檢測腳本,比如可以嘗試解析這個視頻:http://film.qq.com/film/p/topic/thwjlxby/index.html。
當(dāng)未打開開發(fā)者工具進(jìn)行解析,然后打開開發(fā)者工具,則會使用這種檢測方式:
!function() {
var timelimit = 50;
var open = false;
setInterval(function() {
var starttime = new Date();
debugger ;if (new Date() - starttime > timelimit) {
open = true;
window.stop();
$("#loading").hide();
$("#a1").remove();
$("#error").show();
$("#error").html("\u7cfb\u7edf\u68c0\u6d4b\u975e\u6cd5\u8c03\u8bd5\u002c\u8bf7\u5237\u65b0\u91cd\u8bd5\u0021")
} else {
open = false
}
}, 500)
}();
因為這個網(wǎng)站是做vip視頻免費解析的,一旦檢測到有人打開開發(fā)者工具在調(diào)試,就將解析好的視頻移除掉,通過彈出一個提示框:

而當(dāng)已經(jīng)打開開發(fā)者工具再粘貼地址進(jìn)行視頻解析的話,將會觸發(fā)無限debugger。
當(dāng)然,應(yīng)付上面腳本最簡單的方法是把Chrome瀏覽器設(shè)定為Deactive breakpoint,上面的腳本就歇菜了,不過這樣的話自己也沒辦法調(diào)試了,用來反調(diào)試確實能夠惡心一下對面的家伙,比較好的方法是使用Fiddler修改網(wǎng)頁返回內(nèi)容過濾掉debugger標(biāo)簽可以完美破解此套路。
三、檢測窗口大小
檢測窗口大小比較簡單,首先要明確兩個概念,窗口的outer大小和inner大?。?/p>
window.innerWidth / window.innerHeight :可視區(qū)域的寬高,window.innerWidth包含了縱向滾動條的寬度,window.innerHeight包含了水平(橫向)滾動條的寬度。
window.outerWidth / window.outerHeight:會在innerWidth和innerHeight的基礎(chǔ)上加上工具條的寬度。
關(guān)于檢測窗口大小,不再自己寫例子,有人專門針對此寫了個庫:https://github.com/sindresorhus/devtools-detect,畢竟幾百個star,比我這個渣渣寫的好多了,代碼比較簡單,使用部分其github都有說明,這里只對其核心代碼做個分析,此處貼出鄙人對此庫核心代碼的分析:
/* eslint-disable spaced-comment */
/*!
devtools-detect
Detect if DevTools is open
https://github.com/sindresorhus/devtools-detect
by Sindre Sorhus
MIT License
comment by CC11001100
*/
(function () {
'use strict';
var devtools = {
open: false,
orientation: null
};
// inner大小和outer大小超過threshold被認(rèn)為是打開了開發(fā)者工具
var threshold = 160;
// 當(dāng)檢測到開發(fā)者工具后發(fā)出一個事件,外部監(jiān)聽此事件即可,設(shè)計得真好,很好的實現(xiàn)了解耦
var emitEvent = function (state, orientation) {
window.dispatchEvent(new CustomEvent('devtoolschange', {
detail: {
open: state,
orientation: orientation
}
}));
};
// 每500毫秒檢測一次開發(fā)者工具的狀態(tài),當(dāng)狀態(tài)改變時觸發(fā)事件
setInterval(function () {
var widthThreshold = window.outerWidth - window.innerWidth > threshold;
var heightThreshold = window.outerHeight - window.innerHeight > threshold;
var orientation = widthThreshold ? 'vertical' : 'horizontal';
// 第一個條件判斷沒看明白,heightThreshold和widthThreshold不太可能同時為true,不論是其中任意一個false還是兩個都false取反之后都會為true,此表達(dá)式恒為true
if (!(heightThreshold && widthThreshold) &&
// 針對Firebug插件做檢查
((window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized) || widthThreshold || heightThreshold)) {
// 開發(fā)者工具打開,如果之前開發(fā)者工具沒有打開,或者已經(jīng)打開但是靠邊的方向變了才會發(fā)送事件
if (!devtools.open || devtools.orientation !== orientation) {
emitEvent(true, orientation);
}
devtools.open = true;
devtools.orientation = orientation;
} else {
// 開發(fā)者工具沒有打開,如果之前處于打開狀態(tài)則觸發(fā)事件報告狀態(tài)
if (devtools.open) {
emitEvent(false, null);
}
// 將標(biāo)志位恢復(fù)到未打開
devtools.open = false;
devtools.orientation = null;
}
}, 500);
if (typeof module !== 'undefined' && module.exports) {
module.exports = devtools;
} else {
window.devtools = devtools;
}
})();
缺點:
1. 使用window屬性檢查大小可能會有瀏覽器兼容性問題,因為不是專業(yè)前端只測試了Chrome和ff是沒有問題的。
2. 此方案還是有漏洞的,就拿Chrome瀏覽器來說,開發(fā)者工具窗口有四個選項:單獨窗口、靠左、靠下、靠右。
![]()
靠左、靠右、靠下都會占用當(dāng)前窗口的一些空間,這種情況會被檢測到,但是獨立窗口并不會占用打開網(wǎng)頁窗口的空間,所以這種情況是檢測不到的,可去此頁面進(jìn)行驗證:https://sindresorhus.com/devtools-detect/。
四、總結(jié)
本文介紹了幾種檢測方式,其各有利弊,下面是對其缺點的一個簡單的總結(jié):
重寫toString():只能捕獲到開發(fā)者工具從關(guān)閉狀態(tài)向打開狀態(tài)轉(zhuǎn)移的過程
debugger標(biāo)簽:當(dāng)勾選了Chrome瀏覽器的Deactive breakpoint
,debugger標(biāo)簽不會暫停,將捕獲不到
檢測窗口大?。寒?dāng)開發(fā)者工具是以獨立窗口打開的時候不能檢測到
相關(guān)文章
fixedBox固定div漂浮代碼支持ie6以上大部分主流瀏覽器
本例為大家分享的是fixedBox固定div漂浮代碼支持ie6以上大部分瀏覽器,需要的朋友可以參考下2014-06-06
js根據(jù)屬性刪除對象數(shù)組里的相應(yīng)對象
這篇文章主要介紹了js根據(jù)屬性刪除對象數(shù)組里的相應(yīng)對象,需要的朋友可以參考下2023-07-07
使用偽命名空間封裝保護(hù)獨自創(chuàng)建的對象方法
下面小編就為大家?guī)硪黄褂脗蚊臻g封裝保護(hù)獨自創(chuàng)建的對象方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08
JavaScript開發(fā)簡單易懂的Svelte實現(xiàn)原理詳解
這篇文章主要為大家介紹了JavaScript開發(fā)簡單易懂的Svelte實現(xiàn)原理的內(nèi)容詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11

