2020年11月15日

使用 JS 追蹤訪客﹍裝置指紋辨識原理 + 實用工具介紹

使用 JS 追蹤訪客﹍裝置指紋辨識原理 + 實用工具介紹

Wayne Fu 0 A+
現在消費者已越來越能接受線上付費觀看影音服務,例如官方提供的職業運動直播(NBA、MLB 等),以及各種追劇平台(Netflix、Line TV、愛奇藝等)。而消費者總是希望花錢的效益最大化,如果買一個帳號能在三台裝置收看,則可能找三個人合資分攤費用。 站在影音平台的立場,技術上必須防堵消費者同時登入超過 3 個裝置,因此得辨識出個別使用者上網的機器之硬體差異性。那麼這件事要如何做到呢?判斷單一裝置的身份認證,最簡單的方式為獲取「MAC address」,也可看做是該裝置的網卡實體位址,這絕對是獨一無二可供辨識的 ID 字串。 非網頁的環境下取得「MAC 位址」不難,然而線上影音平台架設在網頁,前端使用 Javascript 並沒有權限可取得「MAC 位址」,否則會有安全性問題。那麼網頁環境是否還有辦法能用 JS 追蹤訪客裝置?不然的話消費者買一個帳號卻得以從 100 個裝置登入,這些影音服務就得關門了! 最近有研究網頁裝置辨識的需求,本篇就來整理相關心得,讓每個訪客的裝置都能產生獨有的指紋辨識記錄,並介紹實用的 JS 瀏覽器指紋工具。

一、產生瀏覽器指紋的原理

由於網頁環境不易取得實體裝置資訊,退而求其次產生瀏覽器指紋較為可行。消費者一次只會從裝置使用一個瀏覽器上影音平台,所以辨識瀏覽器指紋已經足夠。 如何辨識使用者的瀏覽器特徵,大致整理出下面這些作法: 1. Google、FB 建立的使用者 ID 其實每個人上網的一舉一動已被 Google、FB 監控,這些大公司早就研發出辨識使用者的技術,在每個人的電腦中埋下記錄。既然輪子已經造好,最簡單的作法是直接拿來用而不是自己研發,只要找出 Google、FB 留下的痕跡即可。 參考這個討論串「How do I uniquely identify computers visiting my web site?」,原 PO 詢問如何辨識他網站的訪客裝置,有人回答 Google Analytics(簡稱GA) 會使用 cookie 留下每個裝置的 ID 字串,讀取這個 ID 字串會是最方便的捷徑。 那麼要如何讀取 GA 留下的 ID 字串呢?這個討論串「how to get the Google analytics client ID」說明了如何讀取 GA 提供的「Client ID」(訪客 ID): ga.getAll()[0].get('clientId'); 使用 JS 執行以上語法就行了,非常簡單吧!同樣的原理,網站如果有安裝 FB pixel 相信也能取得類似的 ID。 只不過這方法雖然簡單,但 cookie 畢竟可能會被清除、修改,比較難讓人安心。 2. Canvas 畫布指紋 最早被利用來製作瀏覽器指紋的技術是 HTML5 的 Canvas 畫布功能,詳細原理可參考這篇「解讀瀏覽器指紋」→「canvas指紋」。 其原理簡單說就是,因每個裝置的軟硬體設備不同,那麼經由 canvas 技術繪製出來的同一幅圖像都會有肉眼看不出的微小差別,而經由演算法就能產生該圖像的一組 ID 字串,可做為瀏覽器指紋之用。 如果不了解這樣的說明,可以閱讀原文的幾張附圖,就能瞭解微小像素在不同裝置會有什麼樣的差異。 3. 綜合指紋 Canvas 指紋已經足以分辨出大部分的使用者裝置,但演算法產生的 ID 字串難免會遇到重複的情形,此時再組合該裝置的其他軟硬體資訊,只要加入的資訊越多,遇到重複的機率就越低。 而其他可以做為指紋的資訊可參考「瀏覽器指紋追蹤技術簡述」:
  • 基本資訊:例如瀏覽器版本、作業系統、語系、時區、螢幕解析度、字體....等等
  • Audio 指紋:類似 Canvas 的作法,利用軟硬體差異,將一段音訊經演算法產生 ID 指紋
  • WebRTC:利用較新瀏覽器版本才支援的功能,來取得經通信技術才能獲得的資訊。
瞭解以上原理後,接下來介紹網路上已開發出的一些 JS 工具,能直接產生個別瀏覽器對應的指紋 ID。

二、FingerprintJS V3

1. 工具介紹 免費開源軟體中功能最強的要屬 FingerprintJS: 以上提供的官網是 V3 版本,官方提供的範例程式碼如下,執行後即可取得瀏覽器辨識 ID 字串。: <script> function initFingerprintJS() { FingerprintJS.load().then(fp => { // The FingerprintJS agent is ready. // Get a visitor identifier when you'd like to. fp.get().then(result => { // This is the visitor identifier: const visitorId = result.visitorId; console.log(visitorId); }); }); } </script> <script async src="//cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@3/dist/fp.min.js" onload="initFingerprintJS()" ></script> 2. 指紋含括資訊 js-track-user-device-fingerprint-1.jpg-使用 JS 追蹤訪客﹍裝置指紋辨識原理 + 實用工具介紹 上圖為 DEMO 頁面提供的資訊,顯示了 FingerprintJS V3 計算瀏覽器指紋 ID 時,所包含的所有資訊,例如瀏覽器版本、語系、記憶體、解析度...等等,截圖只是一部份資訊,螢幕往下捲可看到一共有將近 30 項資訊。 3. 優缺點 此工具的優點為:版本最新,會包含較新的辨識技術,不同裝置的指紋 ID 要重複較困難。 不過從我的角度來看無法做為首選,因為有以下缺點:
  • 新版本沒有 API 使用說明,也就是阻止了前端工程師的開發工作,只能取得指紋 ID,無法進行其他操作
  • 新版本程式碼經過壓縮不易辨識,使用彈性極差,原因在於要另外推廣其付費版本(官網推薦使用者升級為 PRO 版),付費版本可前往「FingerprintJS
  • 新版本一律強制偵測、檢驗所有近 30 種項目,所以 JS 執行時間會比較長一些
因為缺乏彈性、強制檢驗所有項目,這會帶來潛在的缺點,有可能使用者環境設定稍稍有些微變動而已,就會被此工具判定為不同裝置,反而造成開發者的困擾或訪客的麻煩。 我的看法是這樣,有些項目需要檢測的必要性沒那麼大,例如「字型」這一項,除了要花費額外時間檢測,對判定精確度也提升不了多少,還可能導致使用者動了系統的字型設定後,就被此工具判定為不同裝置了。

三、FingerprintJS V2

1. 工具介紹 還好 FingerprintJS 的作者還保留了舊版 V2 的頁面: 這個版本提供了 JS 原始碼,可瞭解程式的運作原理,官網頁面提供了 API 完整操作說明。 所以使用 V2 版本就能彈性檢測必要項目就好,不一定要含括所有將近 30 個項目。 2. 指紋含括資訊 此版本沒有 DEMO 頁面,不過使用官網提供的範例程式碼,就能看到 V2 檢測了哪些項目: <script src='//cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@2/dist/fingerprint2.min.js'> </script> <script> if (window.requestIdleCallback) { requestIdleCallback(function() { Fingerprint2.get(function(components) { console.log(components) // an array of components: {key: ..., value: ...} }) }) } else { setTimeout(function() { Fingerprint2.get(function(components) { console.log(components) // an array of components: {key: ..., value: ...} }) }, 500) } </script> 使用瀏覽器開發人員工具可看到檢測項目: js-track-user-device-fingerprint-2.jpg-使用 JS 追蹤訪客﹍裝置指紋辨識原理 + 實用工具介紹
  • 上圖可與 V3 比對一下,看檢測項目有哪些差異
  • 標示「not available」的項目可考慮不用檢測,因為可能在其他使用者的裝置也是測不出來
  • 「canvas」與「webgl」兩者可考慮選一個就好,都是對繪圖能力做檢驗,但「webgl」含括的資訊量更多
3. 取得指紋 ID 官網文件捲到「get and getPromise」這裡,說明了如何取得瀏覽器指紋 ID: Fingerprint2.get(options, function (components) { var values = components.map(function (component) { return component.value }) var murmur = Fingerprint2.x64hash128(values.join(''), 31) }) murmur 的數值即為「瀏覽器指紋 ID」。 4. 操作補充說明 V2 可對所有檢測項目進行設定,也可排除要檢測的項目,操作說明可在官網文件捲到「Options」,官方提供的範例如下: var options = {fonts: {extendedJsFonts: true}, excludes: {userAgent: true}} 此範例可對「fonts」進行設定,也可排除「userAgent」這一項,細節需參照官方原始碼第 245 行「defaultOptions 」 設定完 options 後就能執行工具,可參考前面「3. 取得指紋 ID」的範例程式碼,該處程式碼即為帶入 options 的結果。

四、canvas 指紋

以前端開發工程師而言,使用「三、FingerprintJS V2」算是最佳選擇。 不過這裡提供另外一個選項,如果覺得研究 V2 的說明書太麻煩,有國外網友從 FingerprintJS 擷取幾個重要項目來使用,檢驗項目算是以 Canvas 為主,發佈在 Github: 所有檢測的項目作者已列在官網,程式碼較短小也節省執行時間,我算是認同此作法。 以下提供此版本整理過的程式碼,直接執行就能取得瀏覽器指紋 ID: var Fingerprint=function(a){var b,c;b=Array.prototype.forEach;c=Array.prototype.map;this.each=function(j,h,g){if(j===null){return}if(b&&j.forEach===b){j.forEach(h,g)}else{if(j.length===+j.length){for(var f=0,d=j.length;f<d;f++){if(h.call(g,j[f],f,j)==={}){return}}}else{for(var e in j){if(j.hasOwnProperty(e)){if(h.call(g,j[e],e,j)==={}){return}}}}}};this.map=function(g,f,e){var d=[];if(g==null){return d}if(c&&g.map===c){return g.map(f,e)}this.each(g,function(j,h,i){d[d.length]=f.call(e,j,h,i)});return d};if(typeof a=="object"){this.hasher=a.hasher;this.canvas=a.canvas}else{if(typeof a=="function"){this.hasher=a}}};Fingerprint.prototype={get:function(){var a=[];a.push(navigator.userAgent);a.push(navigator.language);a.push(screen.colorDepth);a.push(this.getScreenResolution().join("x"));a.push(new Date().getTimezoneOffset());a.push(this.hasSessionStorage());a.push(this.hasLocalStorage());a.push(this.hasIndexDb());if(document.body){a.push(typeof(document.body.addBehavior))}else{a.push(typeof undefined)}a.push(typeof(window.openDatabase));a.push(navigator.cpuClass);a.push(navigator.platform);a.push(navigator.doNotTrack);a.push(this.getPluginsString());if(this.canvas&&this.isCanvasSupported()){a.push(this.getCanvasFingerprint())}if(this.hasher){return this.hasher(a.join("###"),31)}else{return murmurhash3_32_gc(a.join("###"),31)}},hasLocalStorage:function(){try{return !!window.localStorage}catch(a){return true}},hasSessionStorage:function(){try{return !!window.sessionStorage}catch(a){return true}},hasIndexDb:function(){try{return !!window.indexedDB}catch(a){return true}},isCanvasSupported:function(){var a=document.createElement("canvas");return !!(a.getContext&&a.getContext("2d"))},isIE:function(){if(navigator.appName==="Microsoft Internet Explorer"){return true}else{if(navigator.appName==="Netscape"&&/Trident/.test(navigator.userAgent)){return true}}return false},getPluginsString:function(){if(this.isIE()){return this.getIEPluginsString()}else{return this.getRegularPluginsString()}},getRegularPluginsString:function(){return this.map(navigator.plugins,function(b){var a=this.map(b,function(c){return[c.type,c.suffixes].join("~")}).join(",");return[b.name,b.description,a].join("::")},this).join(";")},getIEPluginsString:function(){if(window.ActiveXObject){var a=["ShockwaveFlash.ShockwaveFlash","AcroPDF.PDF","PDF.PdfCtrl","QuickTime.QuickTime","rmocx.RealPlayer G2 Control","rmocx.RealPlayer G2 Control.1","RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)","RealVideo.RealVideo(tm) ActiveX Control (32-bit)","RealPlayer","SWCtl.SWCtl","WMPlayer.OCX","AgControl.AgControl","Skype.Detection"];return this.map(a,function(b){try{new ActiveXObject(b);return b}catch(c){return null}}).join(";")}else{return""}},getScreenResolution:function(){return(screen.height>screen.width)?[screen.height,screen.width]:[screen.width,screen.height]},getCanvasFingerprint:function(){var c=document.createElement("canvas");var b=c.getContext("2d");var a="CANVAS_FINGERPRINT";b.textBaseline="top";b.font="14px 'Arial'";b.textBaseline="alphabetic";b.fillStyle="#f60";b.fillRect(125,1,62,20);b.fillStyle="#069";b.fillText(a,2,15);b.fillStyle="rgba(102, 204, 0, 0.7)";b.fillText(a,4,17);return c.toDataURL()}}; var javaHashCode = function(string, K) {var hash = 0;if (string.length === 0) {return hash;}for (var i = 0; i < string.length; i++) {char = string.charCodeAt(i);hash = K*((hash<<5)-hash)+char;hash = hash & hash;}return hash;}; var fingerprint = new Fingerprint({hasher: javaHashCode}); console.log(fingerprint.get())
更多 Javascript 使用技巧:
0 0
如這篇文章對你有幫助,歡迎「分享」到 FB、「追蹤」粉絲團、「訂閱」最新文章

1 則留言:

張貼留言注意事項:

◎ 勾選「通知我」可收到後續回覆的mail!
◎ 請在相關文章留言,與文章無關的主題可至「Blogger 社團」提問。
◎ 請避免使用 Safari 瀏覽器,否則無法登入 Google 帳號留言(只能匿名留言)!
◎ 提問若無法提供足夠的資訊供判斷,可能會被無視。建議先參考這篇「Blogger 提問技巧及注意事項」。
◎ CSS 相關問題非免費諮詢,建議使用「Chrome 開發人員工具」尋找答案。
◎ 手機版相關問題請參考「Blogger 行動版範本的特質」→「三、行動版範本不一定能執行網頁版工具」;或參考「Blogger 行動版範本修改技巧 」,或本站 Blogger 行動版標籤相關文章。
◎ 非官方範本問題、或貴站為商業網站,請參考「Blogger 免費諮詢 + 付費諮詢
◎ 若是使用官方 RWD 範本,請參考「Blogger 推出全新自適應 RWD 官方範本及佈景主題」→ 不建議對範本進行修改!
◎ 若留言要輸入語法,"<"、">"這兩個符號請用其他符號代替,否則語法會消失!
◎ 為了過濾垃圾留言,所有留言不會即時發佈,請稍待片刻。
◎ 本站「已關閉自刪留言功能」。

TOP