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

2024-06-23 12:52

本文主要是介绍前端小白指南:前端生成唯一设备标识的那些事儿,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最近,我在使用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.chinasem.cn/article/1087211

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

AI一键生成 PPT

AI一键生成 PPT 操作步骤 作为一名打工人,是不是经常需要制作各种PPT来分享我的生活和想法。但是,你们知道,有时候灵感来了,时间却不够用了!😩直到我发现了Kimi AI——一个能够自动生成PPT的神奇助手!🌟 什么是Kimi? 一款月之暗面科技有限公司开发的AI办公工具,帮助用户快速生成高质量的演示文稿。 无论你是职场人士、学生还是教师,Kimi都能够为你的办公文

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

poj 1287 Networking(prim or kruscal最小生成树)

题意给你点与点间距离,求最小生成树。 注意点是,两点之间可能有不同的路,输入的时候选择最小的,和之前有道最短路WA的题目类似。 prim代码: #include<stdio.h>const int MaxN = 51;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int P;int prim(){bool vis[MaxN];

poj 2349 Arctic Network uva 10369(prim or kruscal最小生成树)

题目很麻烦,因为不熟悉最小生成树的算法调试了好久。 感觉网上的题目解释都没说得很清楚,不适合新手。自己写一个。 题意:给你点的坐标,然后两点间可以有两种方式来通信:第一种是卫星通信,第二种是无线电通信。 卫星通信:任何两个有卫星频道的点间都可以直接建立连接,与点间的距离无关; 无线电通信:两个点之间的距离不能超过D,无线电收发器的功率越大,D越大,越昂贵。 计算无线电收发器D