前端小白指南:前端生成唯一设备标识的那些事儿

最近,我在使用javascript开发一个基于Chrome的插件,遇到了一个有意思的需求。插件需要生成一个授权码(code),但为了确保安全性,这个code必须与设备绑定,防止被不同的设备使用,限制一个code只能在一个设备上使用。这个需求带来了一个问题:我该如何在前端中获取当前设备的唯一标识呢?

解决方法

在对浏览器的限制做了进一步了解,因为涉及到用户隐私问题,因为MAC地址是一种物理地址,能够作为设备的唯一标识,如果被网站轻易获取,可能会导致用户隐私泄露和安全风险增加。所以现在浏览器不允许前端JavaScript直接获取设备的MAC地址或其它能够明确标识设备硬件的敏感信息。那么通过前端直接获取设备的唯一硬件标识符是不可能的。至此全剧终,不用往下看了🤣

哈哈哈,虽然不能直接获取到设备唯一信息,但是需求还要做,毕竟解决不了提需求的人啊。在这里整理了几种思路:

  1. 可以通过后端服务器记录IP地址、User-Agent等信息来间接识别用户

  2. 通过收集浏览器的各种配置和环境信息(如屏幕分辨率、字体、插件列表等),生成一个相对唯一的标识符

  3. 在用户首次安装插件时生成一个UUID并存储在Cookie或localStorage中,以此作为该设备的唯一标识。但需注意,用户清除浏览器数据时,此ID存在丢失的风险。

方法一:通过后端服务器记录IP地址、User-Agent等信息

先给大家分析一下这种方法的优缺点 :

优点
  1. 无需在客户端处理复杂逻辑,降低了前端插件的开发复杂性。
  2. 不依赖客户端存储,即使用户清除浏览器数据,也可以识别用户。
缺点

这个方法并不能精确地区分内网中的每个设备,假设现在有10个设备位于同一内网,并通过相同的外网出口IP地址访问互联网时,仅凭后端服务器记录的IP地址无法区分这10个设备,因为它们对外显示的是同一个公网IP地址。

User-Agent,它是浏览器或客户端应用程序发送请求时携带的一个字符串头,包含有关浏览器类型、版本、操作系统等信息。虽然User-Agent可以提供关于用户使用的浏览器和系统的信息,但对于来自同一内网的不同设备,如果这是批量采购的设备,在设备、浏览器和操作系统没有其他差异,它们可能会是相同的User-Agent字符串。

另外就是我们的后台服务只是提供简单的授权码验证服务,增加这部分逻辑显得有些多余。


方法二:生成UUID

可以生成一个随机的 UUID,作为设备的唯一标识符,这个方法完全可以满足需求。

优点:
  • 只要用户不清除浏览器数据,UUID可以长期保留,提供稳定的设备识别能力。
  • 生成UUID并存储在浏览器的localStorage或Cookie中,易于实现。
缺点:
  • 如果用户清除浏览器数据,UUID会丢失,导致无法继续识别设备。
  • 相对于UUID存储在客户端,很容易被用户删除或篡改

示例代码:

// 生成UUID
function generateUUID() {return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);return v.toString(16);});
}

UUID的版本4使用随机数生成,重复的概率极低。然而,如果你担心在非常大的数据集中可能会有重复的情况发生,或者你想要确保在分布式系统中生成的UUID在时间上有一定的顺序,那么将UUID与时间戳结合起来。

// 时间戳可以提供一个大致的创建时间顺序
// UUID可以确保在同一个时间戳内生成的标识符的唯一性
function generateTimestampedUUID() {// 获取当前时间的时间戳(毫秒)const timestamp = Date.now();// 生成UUID v4const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);return v.toString(16);});// 将时间戳和UUID拼接起来return `${timestamp}-${uuid}`;
}console.log(generateTimestampedUUID());

方法三:使用浏览器的信息生成唯一标识符

简单的来说就是,通过JavaScript获取多种客户端信息,这些信息可以用来生成一个相对唯一的标识符,这也是我们常说的“浏览器指纹”。

优点:
  • 较高的唯一性:通过综合多种浏览器和系统信息生成的指纹具有较高的唯一性。
  • 隐蔽性:用户通常不会意识到浏览器指纹的存在,所以不会伪造一些虚假信息。
缺点:
  • 信息变化影响识别:浏览器指纹对用户的设置和环境敏感,用户修改设置后指纹可能会改变。
  • 隐私和法律问题:生成和使用浏览器指纹可能涉及隐私问题,需遵守相关法律法规。万一被抓了就不值得了

那么我们能获取到客户端哪些信息呢?

1、屏幕和显示信息

// 屏幕尺寸:屏幕的宽度和高度。
const screenSize = `${screen.width}x${screen.height}`;// 可视区域尺寸:浏览器窗口的宽度和高度。
const viewportSize = `${window.innerWidth}x${window.innerHeight}`;// 颜色深度:屏幕颜色深度。
const colorDepth = screen.colorDepth;

2、浏览器和操作系统信息

// User-Agent:包含浏览器、操作系统及其版本信息。
const userAgent = navigator.userAgent;// 平台:浏览器运行的系统平台。
const platform = navigator.platform;// 语言:浏览器的语言设置。
const language = navigator.language;

3、浏览器设置和插件信息

// 插件列表:浏览器中安装的插件列表
const plugins = Array.from(navigator.plugins).map(plugin => plugin.name).join(',');// Do Not Track:用户是否启用了“请勿跟踪”设置
const doNotTrack = navigator.doNotTrack;

4、时间和时区信息

// 时区:用户的时区信息
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;// 时区偏移:与UTC的时区偏移,以分钟为单位。
const timeZoneOffset = new Date().getTimezoneOffset();

5、字体信息

// 检测用户系统中可用的字体
const fontList = ['Arial', 'Verdana', 'Times New Roman', 'Courier New', 'Georgia', 'Comic Sans MS', 'Trebuchet MS', 'Arial Black', 'Impact'
];
const availableFonts = [];const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const text = 'abcdefghijklmnopqrstuvwxyz0123456789';fontList.forEach((font) => {context.font = `16px ${font}`;const width = context.measureText(text).width;context.font = `16px monospace`;if (context.measureText(text).width !== width) {availableFonts.push(font);}
});const fonts = availableFonts.join(',');

6、硬件信息

// CPU线程数:设备的逻辑处理器数量。
const hardwareConcurrency = navigator.hardwareConcurrency;// 设备内存:设备内存信息,单位为GB。
const deviceMemory = navigator.deviceMemory;

7、浏览器特性

// WebGL渲染器:WebGL渲染器信息
function getWebGLRenderer() {const canvas = document.createElement('canvas');const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');if (gl) {const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');if (debugInfo) {return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);}}return null;
}const webGLRenderer = getWebGLRenderer();// Canvas指纹:利用Canvas API绘制图像并获取其数据
function getCanvasFingerprint() {const canvas = document.createElement('canvas');const context = canvas.getContext('2d');context.textBaseline = 'top';context.font = '14px Arial';context.textBaseline = 'alphabetic';context.fillStyle = '#f60';context.fillRect(125, 1, 62, 20);context.fillStyle = '#069';context.fillText('Hello, world!', 2, 15);context.fillStyle = 'rgba(102, 204, 0, 0.7)';context.fillText('Hello, world!', 4, 17);return canvas.toDataURL();
}const canvasFingerprint = getCanvasFingerprint();

8、其它特性

// 设备是否支持触控
const touchSupport = 'ontouchstart' in window || navigator.maxTouchPoints > 0;

综合示例代码

最后做一个综合代码示例,将上述信息组合成一个指纹字符串并进行哈希处理,以生成一个唯一标识符:

function generateUUID() {return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);return v.toString(16);});
}function hashString(str) {let hash = 0;for (let i = 0; i < str.length; i++) {const char = str.charCodeAt(i);hash = ((hash << 5) - hash) + char;hash |= 0; // Convert to 32bit integer}return hash.toString(16);
}function getScreenSize() {return `${screen.width}x${screen.height}`;
}function getViewportSize() {return `${window.innerWidth}x${window.innerHeight}`;
}function getFonts() {const fontList = ['Arial', 'Verdana', 'Times New Roman', 'Courier New', 'Georgia', 'Comic Sans MS', 'Trebuchet MS', 'Arial Black', 'Impact'];const availableFonts = [];const canvas = document.createElement('canvas');const context = canvas.getContext('2d');const text = 'abcdefghijklmnopqrstuvwxyz0123456789';fontList.forEach((font) => {context.font = `16px ${font}`;const width = context.measureText(text).width;context.font = `16px monospace`;if (context.measureText(text).width !== width) {availableFonts.push(font);}});return availableFonts.join(',');
}function getWebGLRenderer() {const canvas = document.createElement('canvas');const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');if (gl) {const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');if (debugInfo) {return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);}}return null;
}function getCanvasFingerprint() {const canvas = document.createElement('canvas');const context = canvas.getContext('2d');context.textBaseline = 'top';context.font = '14px Arial';context.textBaseline = 'alphabetic';context.fillStyle = '#f60';context.fillRect(125, 1, 62, 20);context.fillStyle = '#069';context.fillText('Hello, world!', 2, 15);context.fillStyle = 'rgba(102, 204, 0, 0.7)';context.fillText('Hello, world!', 4, 17);return canvas.toDataURL();
}function generateFingerprint() {const screenSize = getScreenSize();const viewportSize = getViewportSize();const colorDepth = screen.colorDepth;const userAgent = navigator.userAgent;const platform = navigator.platform;const language = navigator.language;const plugins = Array.from(navigator.plugins).map(plugin => plugin.name).join(',');const doNotTrack = navigator.doNotTrack;const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;const timeZoneOffset = new Date().getTimezoneOffset();const fonts = getFonts();const hardwareConcurrency = navigator.hardwareConcurrency;const deviceMemory = navigator.deviceMemory;const webGLRenderer = getWebGLRenderer();const canvasFingerprint = getCanvasFingerprint();const touchSupport = 'ontouchstart' in window || navigator.maxTouchPoints > 0;const fingerprint = [screenSize, viewportSize, colorDepth, userAgent, platform, language,plugins, doNotTrack, timeZone, timeZoneOffset, fonts, hardwareConcurrency,deviceMemory, webGLRenderer, canvasFingerprint, touchSupport].join('|');return hashString(fingerprint);
}const fingerprint = generateFingerprint();
const deviceUUID = generateUUID();console.log('Fingerprint:', fingerprint);
console.log('Device UUID:', deviceUUID);

三种方式的适用场景

使用后端服务器记录IP地址和User-Agent
  • 简单的用户追踪:适用于需要记录访问日志或统计用户活动的简单应用。
  • 没有严格的设备绑定要求:适用于不需要严格绑定设备的情况,如某些统计和分析用途。
生成UUID并存储在Cookie或localStorage中
  • 长期设备识别:适用于需要在长时间内稳定识别同一设备的场景,如个性化设置保存、长期会话管理等。
  • 用户行为追踪:适用于需要追踪用户行为和偏好的应用,如个性化推荐、广告投放等。
生成相对唯一的浏览器指纹
  • 增强的安全性:适用于需要更高安全性和防欺骗能力的场景,如防止账号共享、在线考试等。
  • 无用户登录的情况下识别设备:适用于不需要用户登录即可识别和区分不同设备的应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/32496.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python vs MATLAB: 易于学习和代码可理解性的对比

Python vs MATLAB Python vs MATLAB: 易于学习和代码可理解性**Python的易用性与代码理解****MATLAB的易用性与代码理解****哪个更易上手&#xff1f;****结论** Python vs MATLAB: 易于学习和代码可理解性 在科学计算、工程模拟和数据分析领域&#xff0c;Python和MATLAB是两…

使用 C++11 Lambda 表达式在 Qt 中连接信号与槽

在 Qt 使用 C11 引入的 lambda 表达式来简化信号与槽的连接已经成为一种不错的选择。本文将通过一个简单的例子来展示如何使用 lambda 表达式来连接 QAction 的信号与槽。 传统的连接方式 首先&#xff0c;看看传统的连接方式&#xff1a; QAction* action foo->addActi…

腾讯视频通话接口

简要描述 获取登录IM账号及签名请求URL /videocall/getImUserInfo?account=1234&appName=tv请求方式 GET参数 参数名必选类型说明account是string通话双方统一账号appName是string应用名称,区分不同类型应用如:TV,MINI返回示例 {"code": 200,"status"…

kafka 管理节点 Controller 角色分析

kafka 管理节点 Controller 角色分析 kafka controller 如何管理分区的创建、状态监测、故障切换、内容复制、如何管控分区副本的状态检测故障切换、数据同步、learder 选举?Kafka Controller 是 Kafka 集群中的一个关键组件,负责管理分区的创建、状态监测、故障切换、内容 …

document.write()方法总结

document.write()方法总结 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;document.write()方法是JavaScript中用于向HTML文档写入内容的方法之一。它允许我们在…

【团队成长】2024-25周周报-业务介绍内容创作

大家好&#xff01;我们是IndustryOR 团队&#xff0c;致力于分享业界落地的算法技术。欢迎关注微信公众号/知乎/CSDN【运筹匠心】 。 记录人&#xff1a;张哲铭&#xff0c;某互联网大厂算法专家 【团队成长/个人成长】系列的推文会以 【工作周报】 的方式记录IndustryOR团队及…

【SpringCloud-Seata源码分析2】

文章目录 分支事务注册-客户端分支事务服务端的执行 分支事务注册-客户端 第一篇我们将全局事务启动&#xff0c;以及开启源码分析完成了&#xff0c;现在我们需要看一下分支事务注册。 我们分支事务的开始需要从PreparedStatementProxy#executeUpdate中去看。 public class…

智能物流系统堪比帝王宠信妃子,我给你类比说明一下……

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》人俱乐部 让我们将智能物流系统种涉及出库入库作业完整链条的“货到人”拣选系统的工作流程与古代帝王宠信翻牌妃子的过程进行一个有趣的类比&…

【vue3|第13期】深入了解Vue3生命周期:管理组件的诞生、成长与消亡

日期&#xff1a;2024年6月22日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

神经科学原理精解【1】

文章目录 神经系统组成神经系统两类细胞脑组织基本结构参考资料 神经系统组成 神经系统由中驱神经系统和外围神经系统组成。中驱神经系统包括脑和脊髓。脑的主要功能是大脑、小脑和脑干。外围神经系统由位于脑和脊髓之外的神经和神经细胞组成。 神经系统两类细胞 神经元 感知…

基于深度学习的边缘检测

基于深度学习的边缘检测 边缘检测是计算机视觉中的一项基本任务&#xff0c;旨在识别图像中像素值变化显著的区域&#xff0c;即边缘。传统的边缘检测算法&#xff08;如Sobel、Canny等&#xff09;通过滤波器和梯度运算来检测边缘&#xff0c;而基于深度学习的方法则通过训练…

【SSM】医疗健康平台-管理端-检查组管理

技能目标 掌握新增检查组功能的实现 掌握查询检查组功能的实现 掌握编辑检查组功能的实现 掌握删除检查组功能的实现 体检的检查项种类繁多&#xff0c;为了方便管理和快速筛选出类别相同的检查项&#xff0c;医疗健康将类别相同的检查项放到同一个检查组中进行管理&#…

VMware ESXi 主机的健康检查常用命令

使用root登录esxi 主机&#xff0c;然后运行下面的一些命令&#xff0c;可以对ESXi的健康状态有个基本了解&#xff1a; 检查主机资源利用情况&#xff1a; esxtop: 实时查看主机资源使用情况&#xff0c;包括CPU、内存、磁盘和网络。esxcli vm process list: 列出当前在主机上…

【CV炼丹师勇闯力扣训练营 Day8】

CV炼丹师勇闯力扣训练营 代码随想录算法训练营第8天 ● 344.反转字符串 ● 541. 反转字符串II ● 卡码网&#xff1a;54.替换数字 一、344 反转字符串 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额…

# Kafka_深入探秘者(1):初识 kafka

Kafka_深入探秘者&#xff08;1&#xff09;&#xff1a;初识 kafka 一、kafka 特性 1、Kafka &#xff1a;最初是由 Linkedln 公司采用 Scala 语言开发的一个多分区、多副本并且基于 ZooKeeper 协调的分布式消息系统&#xff0c;现在已经捐献给了 Apache 基金会。目前 Kafka…

Python基础教程(三十一):pyecharts模块

💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 💝💝💝如有需要请大家订阅我的专栏【Python系列】哟!我会定期更新相关系列的文章 💝💝💝关注!关注!!请…

代码随想录leetcode200题之单调栈

目录 1 介绍2 训练3 参考 1 介绍 本博客用来记录代码随想录leetcode200题之单调栈相关题目。 2 训练 题目1&#xff1a;739. 每日温度 解题思路&#xff1a;单调栈模型–找到数组中下一个更大数。从右到左遍历&#xff0c;保留更大值&#xff0c;因此是一个单调递减的栈。 …

MyBatis-Plus入门教程(一)

MyBatis-Plus 是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上为其提供了许多便捷功能&#xff0c;使开发者能够更快速、高效地进行数据库操作。 MyBatis-Plus 简介 1. 什么是 MyBatis-Plus&#xff1f; MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBa…

google-自我插件

1. Bitwarden 密码管理器 2. React Developer Tools 3. Vue.js devtools 4. YouTube™ 双字幕 5. 沉浸式翻译 - 网页翻译插件 6. FeHelper(前端助手) 7. IDM Integration Module 8. 待续…

如何使用kimi智能助手:您的智能生活小助手

Kimi智能助手是一款功能强大的AI工具&#xff0c;旨在帮助用户提高工作效率和生活品质。下面小编将详细介绍如何使用Kimi智能助手&#xff0c;涵盖其主要功能以及一些实用技巧。 一、Kimi智能助手的主要功能 多语言对话能力&#xff1a;Kimi擅长中文和英文的对话&#xff0c;可…