没有Cookie或本地存储的用户识别

我正在构build一个分析工具,我目前可以从其用户代理获取用户的IP地址,浏览器和操作系统。

我想知道是否有可能不使用cookie或本地存储检测同一用户? 我不期待在这里的代码示例; 只是一个简单的提示,在哪里看得更远。

忘了提及,如果它是相同的计算机/设备,它将需要跨浏览器兼容。 基本上我是在设备识别之后不是真正的用户。

介绍

如果我正确地理解了你,你需要确定一个你没有唯一标识符的用户,所以你想通过匹配随机数据来找出他们是谁。 您不能可靠地存储用户的身份,因为:

  • Cookies可以被删除
  • IP地址可以更改
  • 浏览器可以改变
  • 浏览器caching可能被删除

一个Java小程序或Com对象本来就是一个使用硬件信息散列的简单解决scheme,但是现在人们非常安全,很难让人们在他们的系统上安装这些types的程序。 这使您坚持使用Cookie和其他类似的工具。

Cookie和其他类似的工具

您可以考虑构build数据configuration文件,然后使用概率testing来识别可能的用户 。 一个有用的configuration文件可以通过下面的一些组合产生:

  1. IP地址
    • 真实的IP地址
    • 代理IP地址(用户经常使用相同的代理)
  2. cookies
    • HTTP Cookie
    • 会话Cookie
    • 第三方Cookie
    • 闪存cookies( 大多数人不知道如何删除这些 )
  3. Web Bug(不太可靠,因为错误得到修复,但仍然有用)
    • PDF错误
    • Flash Bug
    • Java错误
  4. 浏览器
    • 点击跟踪(许多用户在每次访问时访问同一系列页面)
    • 浏览器指纹 – 安装的插件(人们通常有不同的,有点独特的插件集)
    • caching的图像(人们有时删除他们的cookie,但留下caching的图像)
    • 使用Blob
    • URL(浏览器历史logging或Cookie可能在URL中包含唯一的用户标识,例如https://stackoverflow.com/users/1226894或http://www.facebook.com/barackobama?fref=ts
    • 系统字体检测 (这是一个鲜为人知但通常是唯一的密钥签名)
  5. HTML5和Javascript
    • HTML5 LocalStorage
    • HTML5地理位置API和反向地理编码
    • 体系结构,操作系统语言,系统时间,屏幕分辨率等
    • networking信息API
    • 电池状态API

我列出的项目当然是用户可以唯一识别的几种可能的方式。 还有更多。

使用这组随机数据元素来构build数据configuration文件,下一步是什么?

下一步是开发一些模糊逻辑 ,或者更好的是人工neural network (使用模糊逻辑)。 无论哪种情况,这个想法都是训练你的系统,然后把它的训练和贝叶斯推理结合起来,以提高结果的准确性。

人工神经网络

PHP的NeuralMesh库允许您生成人工neural network。 要实现贝叶斯推理,请查看以下链接:

  • 使用PHP实现贝叶斯推断,第1部分
  • 使用PHP实现贝叶斯推断,第2部分
  • 使用PHP实现贝叶斯推断,第3部分

在这一点上,你可能会想:

为什么这么多的math和逻辑看似简单的任务?

基本上,因为这不是一个简单的任务 。 实际上,你试图达到的是纯粹的概率 。 例如,给定以下已知的用户:

User1 = A + B + C + D + G + K User2 = C + D + I + J + K + F 

当您收到以下数据时:

 B + C + E + G + F + K 

你基本上要问的问题是:

接收到的数据(B + C + E + G + F + K)实际上是User1或User2的概率是多less? 那两场比赛哪一场有可能?

为了有效地回答这个问题,您需要了解频率与概率格式以及为什么联合概率可能是更好的方法。 这里的细节太多了(这就是为什么我给你的链接),但一个很好的例子是一个医疗诊断向导应用程序 ,它使用症状的组合来识别可能的疾病。

想象一下构成您的数据档案(上述例子中的B + C + E + G + F + K)作为症状的一系列数据点,以及作为疾病的未知用户。 通过识别疾病,你可以进一步确定一个适当的治疗方法(把这个用户当作User1)。

很明显,我们发现一种以上症状疾病更容易识别。 事实上,我们可以识别的症状越多,我们的诊断就越容易和准确。

还有其他的select吗?

当然。 作为替代措施,您可以创build自己的简单评分algorithm,并将其基于完全匹​​配。 这并不像概率那么高效,但是对于您来说可能更简单。

作为一个例子,考虑这个简单的分数图:

 + ------------------------- + -------- + ------------ +
 | 属性| 重量| 重要性|
 + ------------------------- + -------- + ------------ +
 | 真实IP地址|  60 |  5 |
 | 使用的代理IP地址|  40 |  4 |
 |  HTTP Cookies |  80 |  8 |
 | 会话Cookie |  80 |  6 |
 | 第三方Cookie |  60 |  4 |
 |  Flash Cookies |  90 |  7 |
 |  PDF错误|  20 |  1 |
 |  Flash Bug |  20 |  1 |
 |  Java Bug |  20 |  1 |
 | 频繁页面|  40 |  1 |
 | 浏览器指纹|  35 |  2 |
 | 已安装的插件|  25 |  1 |
 | caching的图像|  40 |  3 |
 | url|  60 |  4 |
 | 系统字体检测|  70 |  4 |
 | 本地存储|  90 |  8 |
 | 地理位置|  70 |  6 |
 |  AOLTR |  70 |  4 |
 | networking信息API |  40 |  3 |
 | 电池状态API |  20 |  1 |
 + ------------------------- + -------- + ------------ +

对于您可以在给定请求中收集的每条信息,授予相关分数,然后使用“ 重要性”在分数相同时解决冲突。

概念validation

对于一个简单的概念certificate,请看Perceptron 。 感知器是通常用于模式识别应用的RNA模型 。 甚至有一个完美实现它的旧PHP类 ,但你可能需要修改它为你的目的。

Perceptron尽pipe是一个很棒的工具,但仍然可以返回多个结果(可能的匹配),所以使用Score和Difference比较对于识别这些匹配中的最好结果仍然是有用的。

假设

  • 存储关于每个用户的所有可能的信息(IP,cookies等)
  • 如果结果完全匹配,则将分数提高1
  • 如果结果不完全匹配,请将分数降低1

期望

  1. 生成RNA标签
  2. 生成模拟数据库的随机用户
  3. 生成一个未知的用户
  4. 生成未知的用户RNA和值
  5. 系统将合并RNA信息并教授感知器
  6. 感知器训练完毕后,系统会有一组权重
  7. 你现在可以testingUnknown用户的模式,Perceptron将产生一个结果集。
  8. 存储所有正面匹配
  9. 首先按比分对比赛进行sorting,然后按差分(如上所述)
  10. 输出两个最接近的匹配,或者,如果找不到匹配,则输出空结果

概念validation码

 $features = array( 'Real IP address' => .5, 'Used proxy IP address' => .4, 'HTTP Cookies' => .9, 'Session Cookies' => .6, '3rd Party Cookies' => .6, 'Flash Cookies' => .7, 'PDF Bug' => .2, 'Flash Bug' => .2, 'Java Bug' => .2, 'Frequent Pages' => .3, 'Browsers Finger Print' => .3, 'Installed Plugins' => .2, 'URL' => .5, 'Cached PNG' => .4, 'System Fonts Detection' => .6, 'Localstorage' => .8, 'Geolocation' => .6, 'AOLTR' => .4, 'Network Information API' => .3, 'Battery Status API' => .2 ); // Get RNA Lables $labels = array(); $n = 1; foreach ($features as $k => $v) { $labels[$k] = "x" . $n; $n ++; } // Create Users $users = array(); for($i = 0, $name = "A"; $i < 5; $i ++, $name ++) { $users[] = new Profile($name, $features); } // Generate Unknown User $unknown = new Profile("Unknown", $features); // Generate Unknown RNA $unknownRNA = array( 0 => array("o" => 1), 1 => array("o" => - 1) ); // Create RNA Values foreach ($unknown->data as $item => $point) { $unknownRNA[0][$labels[$item]] = $point; $unknownRNA[1][$labels[$item]] = (- 1 * $point); } // Start Perception Class $perceptron = new Perceptron(); // Train Results $trainResult = $perceptron->train($unknownRNA, 1, 1); // Find matches foreach ($users as $name => &$profile) { // Use shorter labels $data = array_combine($labels, $profile->data); if ($perceptron->testCase($data, $trainResult) == true) { $score = $diff = 0; // Determing the score and diffrennce foreach ($unknown->data as $item => $found) { if ($unknown->data[$item] === $profile->data[$item]) { if ($profile->data[$item] > 0) { $score += $features[$item]; } else { $diff += $features[$item]; } } } // Ser score and diff $profile->setScore($score, $diff); $matchs[] = $profile; } } // Sort bases on score and Output if (count($matchs) > 1) { usort($matchs, function ($a, $b) { // If score is the same use diffrence if ($a->score == $b->score) { // Lower the diffrence the better return $a->diff == $b->diff ? 0 : ($a->diff > $b->diff ? 1 : - 1); } // The higher the score the better return $a->score > $b->score ? - 1 : 1; }); echo "<br />Possible Match ", implode(",", array_slice(array_map(function ($v) { return sprintf(" %s (%0.4f|%0.4f) ", $v->name, $v->score,$v->diff); }, $matchs), 0, 2)); } else { echo "<br />No match Found "; } 

输出:

 Possible Match D (0.7416|0.16853),C (0.5393|0.2809) 

“D”的Print_r:

 echo "<pre>"; print_r($matchs[0]); Profile Object( [name] => D [data] => Array ( [Real IP address] => -1 [Used proxy IP address] => -1 [HTTP Cookies] => 1 [Session Cookies] => 1 [3rd Party Cookies] => 1 [Flash Cookies] => 1 [PDF Bug] => 1 [Flash Bug] => 1 [Java Bug] => -1 [Frequent Pages] => 1 [Browsers Finger Print] => -1 [Installed Plugins] => 1 [URL] => -1 [Cached PNG] => 1 [System Fonts Detection] => 1 [Localstorage] => -1 [Geolocation] => -1 [AOLTR] => 1 [Network Information API] => -1 [Battery Status API] => -1 ) [score] => 0.74157303370787 [diff] => 0.1685393258427 [base] => 8.9 ) 

如果Debug = true,您将能够看到input(传感器和期望),初始权重,输出(传感器,总和,networking),错误,校正和最终权重 。

 +----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+ | o | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | x10 | x11 | x12 | x13 | x14 | x15 | x16 | x17 | x18 | x19 | x20 | Bias | Yin | Y | deltaW1 | deltaW2 | deltaW3 | deltaW4 | deltaW5 | deltaW6 | deltaW7 | deltaW8 | deltaW9 | deltaW10 | deltaW11 | deltaW12 | deltaW13 | deltaW14 | deltaW15 | deltaW16 | deltaW17 | deltaW18 | deltaW19 | deltaW20 | W1 | W2 | W3 | W4 | W5 | W6 | W7 | W8 | W9 | W10 | W11 | W12 | W13 | W14 | W15 | W16 | W17 | W18 | W19 | W20 | deltaBias | +----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+ | 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 0 | -1 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | -1 | -1 | 1 | -19 | -1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | | 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 19 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | -1 | -1 | 1 | -19 | -1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | +----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+ 

x1到x20代表由代码转换的function。

 // Get RNA Labels $labels = array(); $n = 1; foreach ( $features as $k => $v ) { $labels[$k] = "x" . $n; $n ++; } 

这是一个在线演示

使用的类:

 class Profile { public $name, $data = array(), $score, $diff, $base; function __construct($name, array $importance) { $values = array(-1, 1); // Perception values $this->name = $name; foreach ($importance as $item => $point) { // Generate Random true/false for real Items $this->data[$item] = $values[mt_rand(0, 1)]; } $this->base = array_sum($importance); } public function setScore($score, $diff) { $this->score = $score / $this->base; $this->diff = $diff / $this->base; } } 

修改的感知器类

 class Perceptron { private $w = array(); private $dw = array(); public $debug = false; private function initialize($colums) { // Initialize perceptron vars for($i = 1; $i <= $colums; $i ++) { // weighting vars $this->w[$i] = 0; $this->dw[$i] = 0; } } function train($input, $alpha, $teta) { $colums = count($input[0]) - 1; $weightCache = array_fill(1, $colums, 0); $checkpoints = array(); $keepTrainning = true; // Initialize RNA vars $this->initialize(count($input[0]) - 1); $just_started = true; $totalRun = 0; $yin = 0; // Trains RNA until it gets stable while ($keepTrainning == true) { // Sweeps each row of the input subject foreach ($input as $row_counter => $row_data) { // Finds out the number of columns the input has $n_columns = count($row_data) - 1; // Calculates Yin $yin = 0; for($i = 1; $i <= $n_columns; $i ++) { $yin += $row_data["x" . $i] * $weightCache[$i]; } // Calculates Real Output $Y = ($yin <= 1) ? - 1 : 1; // Sweeps columns ... $checkpoints[$row_counter] = 0; for($i = 1; $i <= $n_columns; $i ++) { /** DELTAS **/ // Is it the first row? if ($just_started == true) { $this->dw[$i] = $weightCache[$i]; $just_started = false; // Found desired output? } elseif ($Y == $row_data["o"]) { $this->dw[$i] = 0; // Calculates Delta Ws } else { $this->dw[$i] = $row_data["x" . $i] * $row_data["o"]; } /** WEIGHTS **/ // Calculate Weights $this->w[$i] = $this->dw[$i] + $weightCache[$i]; $weightCache[$i] = $this->w[$i]; /** CHECK-POINT **/ $checkpoints[$row_counter] += $this->w[$i]; } // END - for foreach ($this->w as $index => $w_item) { $debug_w["W" . $index] = $w_item; $debug_dw["deltaW" . $index] = $this->dw[$index]; } // Special for script debugging $debug_vars[] = array_merge($row_data, array( "Bias" => 1, "Yin" => $yin, "Y" => $Y ), $debug_dw, $debug_w, array( "deltaBias" => 1 )); } // END - foreach // Special for script debugging $empty_data_row = array(); for($i = 1; $i <= $n_columns; $i ++) { $empty_data_row["x" . $i] = "--"; $empty_data_row["W" . $i] = "--"; $empty_data_row["deltaW" . $i] = "--"; } $debug_vars[] = array_merge($empty_data_row, array( "o" => "--", "Bias" => "--", "Yin" => "--", "Y" => "--", "deltaBias" => "--" )); // Counts training times $totalRun ++; // Now checks if the RNA is stable already $referer_value = end($checkpoints); // if all rows match the desired output ... $sum = array_sum($checkpoints); $n_rows = count($checkpoints); if ($totalRun > 1 && ($sum / $n_rows) == $referer_value) { $keepTrainning = false; } } // END - while // Prepares the final result $result = array(); for($i = 1; $i <= $n_columns; $i ++) { $result["w" . $i] = $this->w[$i]; } $this->debug($this->print_html_table($debug_vars)); return $result; } // END - train function testCase($input, $results) { // Sweeps input columns $result = 0; $i = 1; foreach ($input as $column_value) { // Calculates teste Y $result += $results["w" . $i] * $column_value; $i ++; } // Checks in each class the test fits return ($result > 0) ? true : false; } // END - test_class // Returns the html code of a html table base on a hash array function print_html_table($array) { $html = ""; $inner_html = ""; $table_header_composed = false; $table_header = array(); // Builds table contents foreach ($array as $array_item) { $inner_html .= "<tr>\n"; foreach ( $array_item as $array_col_label => $array_col ) { $inner_html .= "<td>\n"; $inner_html .= $array_col; $inner_html .= "</td>\n"; if ($table_header_composed == false) { $table_header[] = $array_col_label; } } $table_header_composed = true; $inner_html .= "</tr>\n"; } // Builds full table $html = "<table border=1>\n"; $html .= "<tr>\n"; foreach ($table_header as $table_header_item) { $html .= "<td>\n"; $html .= "<b>" . $table_header_item . "</b>"; $html .= "</td>\n"; } $html .= "</tr>\n"; $html .= $inner_html . "</table>"; return $html; } // END - print_html_table // Debug function function debug($message) { if ($this->debug == true) { echo "<b>DEBUG:</b> $message"; } } // END - debug } // END - class 

结论

识别没有唯一标识符的用户不是一个简单或简单的任务。 它依赖于收集足够数量的随机数据,您可以通过多种方法从用户那里收集这些数据。

即使你select不使用人工neural network,我也build议至less使用一个简单的概率matrix来表示优先级和可能性 – 我希望上面提供的代码和例子足以让你继续。

这种技术(检测没有cookie的同一用户 – 甚至没有ip地址)被称为浏览器指纹 。 基本上,您可以抓取有关浏览器的信息 – 使用JavaScript,Flash或Java(f.ex.安装的扩展名,字体等)可以获得更好的结果。 之后,如果需要,可以将结果散列存储。

这是不可靠的,但是:

83.6%的浏览者有独特的指纹; 在启用了Flash或Java的用户中,为94.2%。 这不包括cookies!

更多信息:

上面提到的拇指打印工作,但仍然可能遭受歧视。

一种方法是将UID添加到与用户的每次交互的URL中。

http://someplace.com/12899823/user/profile

网站中的每个链接都适用于此修饰符。 这与ASP.Net用于在页面之间使用FORM数据的方式类似。

你看了Evercookie ? 它可能或不可以跨浏览器工作。 从他们的网站摘录。

“如果用户在一个浏览器上煮熟并切换到另一个浏览器,只要他们仍然有本地共享对象的cookie,cookie将在两个浏览器中重现。

你可以用一个caching的png来做到这一点,这将是有点不可靠(不同的浏览器行为不同,如果用户清除caching,它将失败),但这是一个选项。

1:build立一个数据库,存储一个唯一的用户id作为hexstring

2:创build一个genUser.php(或任何语言)文件,生成一个用户ID,将其存储在数据库中,然后创build一个真正的颜色.png的hexstring的值(每个像素将是4字节),并返回即浏览器。 一定要设置内容types和caching标题。

3:在HTML或JS中创build一个像<img id='user_id' src='genUser.php' />

4:将该图像绘制到canvasctx.drawImage(document.getElementById('user_id'), 0, 0);

5:使用ctx.getImageData读取该图像的字节,并将整数转换为hexstring。

6:这是您现在caching在用户计算机上的唯一用户标识。

根据你所说的话:

基本上我是在设备识别之后不是真正的用户

最好的办法是发送作为NIC ID的MAC地址。

你可以看看这篇文章: 我怎样才能获得一个连接客户端的MAC和IP地址在PHP中?

JavaScript的Macsearch器

你可以用etags来完成。 虽然我不确定这个法律是否是一堆诉讼案件。

如果你正确地警告你的用户,或者你有类似的内部网站这样的东西,那也可以。

您可能会创build一个blob来存储设备标识符…

缺点是用户需要下载blob( 可以强制下载 ),因为浏览器无法访问文件系统来直接保存文件。

参考:

https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-22/blobs

效率低下,但可能会给你想要的结果,将轮询一个API在你身边。 在间隔发送用户数据的客户端有一个后台进程。 您将需要一个用户标识符发送到您的API。 一旦你有,你可以发送任何信息关联到唯一的标识符。

这消除了对cookie和本地存储的需求。

我不敢相信, http://browserspy.dk在这里还没有提到!; 该网站描述了许多特征(在模式识别方面),可以用来build立一个分类器。

而原因,评估function,我会build议特别支持向量机和libsvm 。

跟踪他们在一个会议或跨会议?

如果您的站点是HTTPS Everywhere,则可以使用TLS会话ID来跟踪用户的会话

  1. 创build一个跨平台的虚拟(nsapi)插件,并在用户下载插件时(例如login后)为插件名称或版本生成一个唯一的名称。
  2. 为插件提供安装程序/根据策略进行安装

这将要求用户自愿安装标识符。

一旦插件安装,任何(插件启用)浏览器的指纹将包含这个特定的插件。 要将信息返回给服务器,需要在客户端有效检测插件的algorithm,否则IE和Firefox> = 28的用户将需要一个可能的有效标识表。

这需要对可能被浏览器供应商closures的技术进行相对高的投资。 当你能够说服你的用户安装一个插件时,可能还会有一些选项,例如安装本地代理 ,使用vpn或修补networking驱动程序。

不想被识别的用户(或他们的机器)总是会find一种方法来防止它。