對於數據統計分析或者數據挖掘而言,用戶是個很是重要的維度,也是統計分析能落地的基礎。通常而言,我們追蹤或者識別一個用戶的首選方案是 userID,大多數公司的產品都會要求用戶註冊、登陸操做,都存在一個相似 UMC 的數據庫,管理和標示全部的用戶。但這有個前提條件,就是你所在的公司業務必須以閉環爲主(好比 qq、微信、淘寶等)。若是產品沒有造成閉環,用戶就不會主動去註冊、登陸,那上面經過 userID 數據庫來管理、追蹤用戶行爲的方案就不行了。好比BBS站點或者廣告聯盟都會很是想要一種技術方式能夠在網絡上精肯定位到每個個體,這樣能夠經過收集這些個體的數據,經過分析後更加精準的去推送廣告(精準化營銷)或其餘有針對性的一些活動。當用戶訪問一個網站時,網站生成一個含有惟一標示符(UUID)的信息,並經過這個信息將用戶全部行爲(瀏覽了哪些頁面?搜索了哪些關鍵字?對什麼感興趣?點了哪些按鈕?用了哪些功能?看了哪些商品?把哪些放入了購物車等等)關聯起來。那這種狀況下有沒有可能有其它的技術方案去管理追蹤這種遊客態用戶呢?
javascript
答案或許不少同窗會回答用 cookie。是的,對於遊客態用戶而言,經常使用的身份識別方案就是使用 cookie,技術實現難度小,成本相對很低廉。那是否是使用 cookie 就萬事大吉了呢?準確性、穩定性、可辨識性怎麼樣?下面我們就來深刻探討下 cookie 追蹤用戶的利弊及其發展與移動互聯網時代下用戶身份識別面臨的新問題。php
先上一張圖,能夠看到大部分流行的方法仍是基於Cookie,只是這些 Cookie 會稍有不一樣,本文會按照整張圖的脈絡來一一介紹各類 cookie 及其利與弊。css
爲何會有 HTTPCookie 呢?由於HTTP協議是無狀態的,即服務器不知道用戶上一次作了什麼,這嚴重阻礙了交互式Web應用程序的實現。在典型的網上購物場景中,用戶瀏覽了幾個頁面,買了一盒餅乾和兩瓶飲料。最後結賬時,因爲HTTP的無狀態性,不經過額外的手段,服務器並不知道用戶到底買了什麼。 因此Cookie就是用來繞開HTTP的無狀態性的「額外手段」之一。服務器能夠設置或讀取Cookies中包含信息,藉此維護用戶跟服務器會話中的狀態。html
Cookie是由服務器端生成(webserver或者cgi),response 給User-Agent(通常是瀏覽器),瀏覽器會將Cookie的key/value保存到某個目錄下的文本文件內,下次請求同一網站時就發送該Cookie給服務器(前提是瀏覽器設置爲啓用cookie)。前端
Cookie會被附加在每一個HTTP請求中,因此無形中增長了流量。java
因爲在HTTP請求中的Cookie是明文傳遞的,因此安全性成問題。(除非用HTTPS)python
Cookie的大小限制在4KB左右。對於複雜的存儲需求來講是不夠用的。git
瀏覽器安全策略不容許種植 cookieID、用戶清除 cookieID,不一樣的瀏覽器也會生成不一樣的 cookieID,大量的爬蟲也可能帶上隨機 cookieIDgithub
識別不許確:瀏覽器安全策略不容許種植 cookieID、用戶清除 cookieID,不一樣的瀏覽器生成不一樣的 cookieID,大量的爬蟲帶上隨機 cookieID
web
在客戶端Cookie裏保存數據是不穩 定的,由於用戶可能隨時會清除掉瀏覽器的Cookie,在這種狀況下,通常的解決方案是從新向服務器端發送一個請求,以得到一個新的HTTP Cookie數據,並將其保存,就通常的交互需求而言,這是沒有問題的。可是,假若個人需求是:要求恢復到原來的Cookie裏保存數據持久的追蹤用戶的行爲呢?這種狀況,假若服務器端沒有作特殊的處理的話,顯然是很難實現的,這裏就該 Flash Cookie 登場了。
FlashCookie是由FlashPlayer控制的客戶端共享存儲技術,它具有如下特色:
相似 HTTPCookie,FlashCookie利用SharedObject類實現本地存儲信息,SharedObject類用於在用戶計算機上讀取和存 儲有限的數據量,共享對象提供永久貯存在用戶計算機上的對象之間的全局實時數據共享;
本地共享對象是做爲一些單獨的文件來存儲的,它們的文件擴展名 爲.SOL。默認時,它們的尺寸爲不超過100kB,而且不會過時——這一點與傳統的HTTP Cookie不一樣(4KB);
本地共享對象並非基於瀏覽器的,因此普通的用戶不容易刪除它們。若是要刪掉它們的話,首先要知道這些文件所在的具體位 置。這使得本地共享對象可以長時間的保留在本地系統上。
要實現Flash Cookie永遠存儲的功能,顯然,首先要實現Flash Cookie與Http Cookie的互通,因此,在技術上使用JavaScript與ActionScript的來進行溝通顯然是最好的選擇,由於在這兩種語言之間,除了語法 上相近,從溝通上也有着完美的實現。下面咱們來看看實現流程(如圖所示):
這裏給一個實現的 demo,點窗口→動做,咱們就能夠寫actionscript3的代碼了,而後文件→發佈成 .swf 文件:
//導入ExternalInterface類 import flash.external.ExternalInterface; flash.system.Security.allowDomain("http://localhost"); flash.system.Security.allowDomain("http://127.0.0.1"); //容許任何域均可以訪問 flash.system.Security.allowDomain("*"); function setFC(userName:String, sex:String) { var FlashCookie:SharedObject = SharedObject.getLocal("testFlashCookie"); FlashCookie.data.cookie["userName"] = userName; FlashCookie.data.cookie["sex"] = sex; FlashCookie.flush(); } function getFC():String { var FlashCookie:SharedObject = SharedObject.getLocal("testFlashCookie"); return FlashCookie.data.cookie["userName"]; } function setFCUserObj(obj:Object) { var FlashCookie:SharedObject = SharedObject.getLocal("testFlashCookie"); if (FlashCookie.data.cookie == undefined) { //var obj:Object = {}; //obj[key] = value; FlashCookie.data.cookie = obj; } else { for (var key:String in obj) { FlashCookie.data.cookie[key] = obj[key]; } } //FlashCookie.data.userName = obj.userName; //FlashCookie.data.sex = obj.sex; FlashCookie.flush(); } //容許js)調用flash中的getFC(),setFC(),setFCUserObj ExternalInterface.addCallback("getFC", getFC); ExternalInterface.addCallback("setFC", setFC); ExternalInterface.addCallback("setFCUserObj", setFCUserObj);
再用 flask 搭一個簡單的頁面:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN"> <head> <title>testFC</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <style type="text/css" media="screen"> html, body { height: 100%; background-color: #ffffff; } body { margin: 0; padding: 0; overflow: hidden; } #flashContent { width: 100%; height: 100%; } </style> <script type="application/javascript"> function setCookie(c_name, value, expiredays) { var exdate = new Date() exdate.setDate(exdate.getDate() + expiredays) document.cookie = c_name + "=" + escape(value) + ((expiredays == null) ? "" : ";expires=" + exdate.toGMTString()) } function getCookie(c_name) { if (document.cookie.length > 0) { c_start = document.cookie.indexOf(c_name + "=") if (c_start != -1) { c_start = c_start + c_name.length + 1 c_end = document.cookie.indexOf(";", c_start) if (c_end == -1) c_end = document.cookie.length return unescape(document.cookie.substring(c_start, c_end)) } } return "" } </script> <script type="text/javascript"> //搭建js與flash互通的環境 function thisMovie() { if (navigator.appName.indexOf("Microsoft") != -1) { return window["testFC"]; } else { return document["testFC"]; } } function setFCUseObj() { c_name = getCookie("userName") c_sex = getCookie("sex") if (c_name == "") { alert("當前 jCookie: " + c_name + "\n" + "當前 flash cookie: " + thisMovie().getFC()) var ajaxRequest = new XMLHttpRequest(); ajaxRequest.open("GET", "http://127.0.0.1:5000/add", false); ajaxRequest.send(null); c_name = getCookie("userName") + Math.random(); c_sex = getCookie("sex") + Math.random(); } {# expiredays = 1#} {# setCookie(key, value, expiredays)#} var obj = new Object(); obj.userName = c_name; obj.sex = c_sex; thisMovie().setFCUserObj(obj); } function getFC() { alert(thisMovie().getFC()); } function setFC() { thisMovie().setFC("June_flashCookie", "male"); } </script> </head> <body> <input type="button" onclick="setFC()" value="setFC"/> <input type="button" onclick="getFC()" value="getFC"/> <input type="button" onclick="setFCUseObj()" value="setFCUseObj"/> <div id="flashContent"> <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0" width="1" height="1" id="testFC" title="testFC"> <param name="allowScriptAccess" value="always"/> <param name="movie" value="testFC.swf"> <param name="quality" value="high"> <param name="wmode" value="transparent"/> <embed src="static/testFC.swf" name="testFC" quality="high" allowScriptAccess="always" swLiveConnect="true" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" width="1" height="1"></embed> </object> </div> </body> </html>
最後再配個簡單的 cgi:
from flask import Flask, request, Response, make_response, render_template import time app = Flask(__name__) @app.route('/') def hello_world(): return 'hello world' @app.route('/add') def login(): res = Response('add cookies') res.set_cookie(key='userName', value='lisi_jsCookie', expires=time.time() + 10 * 60) res.set_cookie(key='sex', value='unKnown', expires=time.time() + 10 * 60) return res @app.route('/testFC') def cookietest(): return render_template("testFC.html") if __name__ == '__main__': app.run(host='0.0.0.0', debug=True)
優點是顯而易見的,缺陷在於:
部署相對 HTTP Cookie 複雜了,存在必定的開發維護成本。
平臺兼容性不夠好,目前只支持特定的平臺與特定的瀏覽器,好比 蘋果的全系列產品都不支持 flash,以致於2012年開始 adobe 已經完全放棄了移動端 flash 的更新,這樣 Android 平臺將來也不會存在 flash 這一技術了,所以至少在移動端 Flash Cookie 不能做爲長久之計。
能夠跨瀏覽器使用,只要瀏覽器調用的是同一個Flash組件,但彼此兼容性不夠(如Internet Explorer和Mozilla Firefox之間信息即可共享,但Google Chrome和Mozilla Firefox便不行)。
雖然能夠跨瀏覽器,但隱身模式無效。能夠被很容易地清除。
前面的兩種方法都存在必定的缺陷,在複雜多變的用戶場景裏,數據可能和真實的偏差很大,那有沒有辦法進一步提升 Cookie 追蹤用戶身份的準確度呢?也有,那就是最後聊到的 EverCookie,其實它也不是什麼新的 cookie 技術,只是利用客戶端各類存儲區域,儘量的存儲多的 cookie 副本,以防某處 cookie 被刪除能夠恢復,至關於 cookie 也有了相似 hadoop 多副本災備機制,同時生成 cookieID 的算法參考了更多的客戶端標識和軟硬件特徵,讓 cookieID 具備更高的穩定性、惟一性、可辨識性,不隨算法自己隨機性的影響。
Evercookie不只僅是難刪除,而是會積極「反抗」刪除。方法就是在用戶電腦裏,利用不一樣的存儲機制不斷地複製本身,或者在副本丟失或到期做廢時讓本身從新復活。具體來講,Evercookie在建立cookie時會使用以下存儲機制:
標準HTTP cookie
Local Shared Objects (Flash cookie)
Silverlight Isolated Storage
以自動生成、強制緩存的PNG像素圖片的RGB值形式保存cookie,使用HTML5 Canvas標籤讀取像素圖片(cookie)
在瀏覽器歷史記錄中存儲cookie
在HTTP ETag中存儲cookie
在瀏覽器緩存中存儲cookie
window.name緩存
Internet Explorer userData
HTML5 Session Storage
HTML5 Local Storage
HTML5 Global Storage
HTML5 Database Storage(SQLite)
開發人員計劃增長以下功能:
HTTP Authentication緩存
使用Java基於NIC信息產生惟一鍵
上面的 EverCookie 看起來氣場十足,很完美,其實只是理想太美好,顯示依舊很殘酷——同樣的存在諸多缺陷,只是它把一些缺陷不足儘量下降了而已。
以 canvas指紋 爲例:
雖然解決了 cookie 的穩定性——沒法刪除,可是惟一性、可辨識性並無解決——重複率過高、ID容易變化:
這裏有個在線指紋測試的例子:
從文初的圖上能夠看到,在 PC 時代,追蹤用戶身份技術方案多,也挺靠譜的,可是隨着移動互聯網大潮的到來,用戶逐漸轉向了 M 和 APP,造成三大平臺三足鼎立的局面,這三大平臺的軟硬件技術方案各異,好比蘋果系列的產品不支持 flash、不容許隨便種植 cookie,而 Android 雖然開放,可是開放的尺度太大了,致使了很嚴重的軟硬件碎片化的問題,這給技術方案的通用兼容性帶來了嚴重的問題。應用又分爲 NativeAPP 和 webAPP,前者能夠很好的和系統結合,拿到系統的硬件信息特徵,好比 MAC、IMEI,而 webAPP 大都受限於瀏覽器隱私策略保護和前端技術限制,無法拿到系統的硬件信息,這就直接致使沒法生成一個基於硬件的惟一的、穩定的、準確的「用戶ID」,並且想要三端用戶身份都打通就成了一個難事,好比:公司三端的用戶重合度是 100%,每端 UV 都是一億,那麼三端的總 UV 應該是一億,可是以現有業界的 cookieID 技術方案來統計 UV,會得出三端總 UV 是三億的錯誤結論,而這目前業界也尚未很完善、通用的解決方案。
總結下在移動互聯網時代,用戶身份識別與追蹤的新挑戰有兩點:
三端用戶身份沒法打通、統一
追蹤識別的成本愈來愈高,方案愈來愈複雜化
這或許是商業行爲與用戶隱私的一場持久博弈,而在這場博弈的背後技術又將會扮演什麼角色呢?
[1] Javascript-Flash-Cookies
https://github.com/nfriedly/Javascript-Flash-Cookies
[2] flash cookie的製做和使用例子詳解 一
http://ylq365.iteye.com/blog/1873382
[3] 用戶數據跟蹤之Flash Cookies
http://www.biaodianfu.com/flash-cookies.html
[4] 使用Flash Cookie技術在客戶端永久保存HTTP Cookie
http://www.cnblogs.com/dcba1112/archive/2011/05/05/2037715.html
[5] 不用Cookie的「Cookie」技術:etag
http://blog.jobbole.com/46266/
[6] 網站數據收集
https://support.google.com/partners/answer/6083646?hl=zh-Hans
[7] php 如何對客戶端 pc 生成惟一標識?
[8] 防惡意點擊代碼系統思路與實現
http://wenku.baidu.com/view/6c0b0749be1e650e52ea9917
[9] Evercookie(永遠刪不掉的cookie)
http://www.ituring.com.cn/article/35102
[9] 如何設置一個永遠沒法刪除的Cookie
http://www.biaodianfu.com/zombie-cookie.html
[9] 關於瀏覽器身份追蹤技術的研究與整理
http://blog.zsxsoft.com/post/11
[10] evercookie
https://github.com/samyk/evercookie
https://github.com/decli/flask-fingerprint
[11] 取代cookie的網站追蹤技術:」帆布指紋識別」初探
http://security.tencent.com/index.php/blog/msg/59
[12] canvas指紋驗證測試報告
http://blog.csdn.net/huangm_fat/article/details/38522939
[13] 在線指紋測試例子:
[14] 自由之設備,獨立之人格:從設備識別到跨屏營銷