FingerprintJS
-
FingerprintJS 是一种浏览器指纹识别工具,可以用来生成设备的唯一标识符。利用浏览器和设备的一系列非敏感数据(如屏幕分辨率、字体、WebGL 信息等)来创建一个高度唯一的指纹,用于追踪和识别用户。通过这种方式,可以实现跨会话、跨设备的用户识别,而不依赖传统的 cookie 方式,尤其适用于防止用户伪装身份或阻止追踪。如果需要更强大的功能(如设备识别的准确性提高或更多分析功能),可以使用 FingerprintJS 的 Pro 版本。
-
开源部分地址:https://github.com/fingerprintjs/fingerprintjs
FingerprintJS 示例代码
- 首先,需要安装 FingerprintJS 的 NPM 包:
npm install @fingerprintjs/fingerprintjs
- FingerprintJS 使用异步加载,因此需要先创建一个实例,然后调用其方法生成指纹。在 React 应用中,可以在组件挂载时生成指纹并存储:
import React, { useEffect, useState } from "react";
import FingerprintJS from "@fingerprintjs/fingerprintjs";
const App = () => {
const [fingerprint, setFingerprint] = useState("");
useEffect(() => {
// 创建 FingerprintJS 加载器实例
const loadFingerprint = async () => {
const fp = await FingerprintJS.load(); // 加载库
const result = await fp.get(); // 获取指纹数据
setFingerprint(result.visitorId); // 保存指纹 ID
};
loadFingerprint();
}, []);
return (
<div>
<h1>Device Fingerprint</h1>
<p>Your fingerprint is: <strong>{fingerprint}</strong></p>
</div>
);
};
export default App;
- 实现效果:
FingerprintJS 实现原理
- FingerprintJS 是一个基于浏览器指纹(browser fingerprinting)的技术,用于唯一地标识用户。它通过收集浏览器和设备的多种信息,通过哈希处理生成一个唯一标识符。
信息收集(数据采集)
- FingerprintJS 通过收集多种浏览器和设备的属性 sources 来生成指纹。 这些属性包括但不限于:
- User-Agent:浏览器类型、版本、操作系统等。
- 屏幕分辨率:用户设备的屏幕分辨率(宽×高)。
- 浏览器插件:已安装的浏览器插件(例如 Adobe Flash、Java、PDF 阅读器等)。
- 语言设置:浏览器设置的语言。
- 时区信息:浏览器的时区。
- 字体:用户设备上安装的字体。
- WebGL 渲染信息:通过 WebGL 获取的 GPU 和硬件信息。
- Canvas 指纹:通过绘制一个不可见的图形(例如通过 Canvas API),不同设备的渲染特性会有所不同,这可以用来生成一个独特的指纹。
- AudioContext:不同设备和浏览器对音频上下文的处理有所不同,可以用来区分设备。
- HTTP 请求头信息:例如,接受的编码类型、语言、缓存控制等。
- LocalStorage 和 SessionStorage:这些存储机制中的数据可能包含一些独特的信息,供指纹生成使用。
- Cookies:可能利用 cookies 中的历史信息来补充识别。
// https://github1s.com/fingerprintjs/fingerprintjs/blob/master/src/sources/index.ts#L51-L102
export const sources = {
// READ FIRST:
// See https://github.com/fingerprintjs/fingerprintjs/blob/master/contributing.md#how-to-add-an-entropy-source
// to learn how entropy source works and how to make your own.
// The sources run in this exact order.
// The asynchronous sources are at the start to run in parallel with other sources.
fonts: getFonts,
domBlockers: getDomBlockers,
fontPreferences: getFontPreferences,
audio: getAudioFingerprint,
screenFrame: getScreenFrame,
canvas: getCanvasFingerprint,
osCpu: getOsCpu,
languages: getLanguages,
colorDepth: getColorDepth,
deviceMemory: getDeviceMemory,
screenResolution: getScreenResolution,
hardwareConcurrency: getHardwareConcurrency,
timezone: getTimezone,
sessionStorage: getSessionStorage,
localStorage: getLocalStorage,
indexedDB: getIndexedDB,
openDatabase: getOpenDatabase,
cpuClass: getCpuClass,
platform: getPlatform,
plugins: getPlugins,
touchSupport: getTouchSupport,
vendor: getVendor,
vendorFlavors: getVendorFlavors,
cookiesEnabled: areCookiesEnabled,
colorGamut: getColorGamut,
invertedColors: areColorsInverted,
forcedColors: areColorsForced,
monochrome: getMonochromeDepth,
contrast: getContrastPreference,
reducedMotion: isMotionReduced,
reducedTransparency: isTransparencyReduced,
hdr: isHDR,
math: getMathFingerprint,
pdfViewerEnabled: isPdfViewerEnabled,
architecture: getArchitecture,
applePay: getApplePayState,
privateClickMeasurement: getPrivateClickMeasurement,
audioBaseLatency: getAudioContextBaseLatency,
// Some sources can affect other sources (e.g. WebGL can affect canvas), so it's important to run these sources
// after other sources.
webGlBasics: getWebGlBasics,
webGlExtensions: getWebGlExtensions,
}
示例:getOsCpu()获取当前操作系统标识
// https://github1s.com/fingerprintjs/fingerprintjs/blob/master/src/sources/os_cpu.ts#L1-L3
export default function getOsCpu(): string | undefined {
return navigator.oscpu // 返回一个字符串,用于标识当前操作系统。https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/oscpu
}
load函数
FingerprintJS
库中的load
函数用于初始化和加载指纹识别所需的组件(确保所有组件和配置都正确加载),并返回一个Agent
实例。- 该函数返回一个
Promise<Agent>
,即一个异步操作,最终返回一个Agent
实例。
// https://github1s.com/fingerprintjs/fingerprintjs/blob/master/src/agent.ts#L195-L206
export async function load(options: Readonly<LoadOptions> = {}): Promise<Agent> {
if ((options as { monitoring?: boolean }).monitoring ?? true) {
monitor()
}
const { delayFallback, debug } = options
await prepareForSources(delayFallback)
const getComponents = loadBuiltinSources({ cache: {}, debug })
return makeAgent(getComponents, debug)// 创建并返回Agent实例
}
-
options
(类型:Readonly<LoadOptions>
):一个可选的配置对象,包含了在加载时需要的一些选项。Readonly
限制,意味着options
对象在传入后不可修改。monitoring
(类型:boolean
,默认值:true
):如果为true
,启用实时监控操作功能。delayFallback
(类型:number
或undefined
,默认值:undefined
):用于设置加载延迟,可能用于解决一些性能问题或延迟加载组件。debug
(类型:boolean
,默认值:false
):是否启用调试模式,输出详细的调试信息。
-
loadBuiltinSources函数:
const getComponents = loadBuiltinSources({ cache: {}, debug })
loadBuiltinSources
是一个加载内置组件的函数。它返回一个函数getComponents
,这个函数可以用来获取加载的组件。
生成指纹(Fingerprint Generation)
- FingerprintJS 会通过算法对收集到的各种信息进行哈希处理,形成一个高概率唯一的标识符。不同的浏览器和设备组合通常会生成不同的指纹。这个指纹会根据不同的属性值变化,因此即使是同一设备的不同浏览器或不同浏览器的同一设备也可能生成不同的指纹。
makeAgent函数
makeAgent
是一个 工厂函数,返回一个包含get()
方法的Agent
对象。Agent
对象负责收集用户的浏览器或设备指纹信息。
// https://github1s.com/fingerprintjs/fingerprintjs/blob/master/src/agent.ts#L141-L174
/**
* The function isn't exported from the index file to not allow to call it without `load()`.
* `makeAgent` 这个函数没有直接从库的主入口文件(`index`)导出,目的是防止在没有执行 `load()` 初始化操作的情况下直接调用它。
* 所以,调用这个函数之前,必须先执行某些必要的加载或初始化步骤。
*
* The hiding gives more freedom for future non-breaking updates.
* 通过将这个函数隐藏起来,库可以在未来进行更新时更加灵活。这样,即使库内部做了改变或优化,也不会影响到外部用户的代码,从而减少破坏性更新的风险。
*
* A factory function is used instead of a class to shorten the attribute names in the minified code.
* 库使用了一个 工厂函数(`makeAgent`),而不是使用类(class),目的是在代码压缩(minification)后,能生成更短的变量名,从而减小文件大小。因为类通常会有较长的属性名,而工厂函数可以提供更精简的结构。
*
* Native private class fields could've been used, but TypeScript doesn't allow them with `"target": "es5"`.
* 兼容性:虽然可以使用原生的私有类字段(比如 `#fieldName`)来封装数据,但由于 TypeScript 编译设置为 `target: "es5"`(即编译为 ECMAScript 5 标准),这时不能使用私有字段。为了解决这个问题,库选择了工厂函数而不是类,避免了兼容性问题。
*/
function makeAgent(getComponents: () => Promise<BuiltinComponents>, debug?: boolean): Agent {
const creationTime = Date.now()
return {
async get(options) {
const startTime = Date.now()
const components = await getComponents()
const result = makeLazyGetResult(components)
if (debug || options?.debug) {
// console.log is ok here because it's under a debug clause
// eslint-disable-next-line no-console
console.log(`Copy the text below to get the debug data:
\`\`\`
version: ${result.version}
userAgent: ${navigator.userAgent}
timeBetweenLoadAndGet: ${startTime - creationTime}
visitorId: ${result.visitorId}
components: ${componentsToDebugString(components)}
\`\`\``)
}
return result
},
}
}
makeLazyGetResult
makeLazyGetResult
函数创建并返回一个GetResult
对象,该对象用于获取指纹识别的结果,并在需要时计算和缓存visitorId
(访客 ID)的哈希值。其设计目的是优化性能,确保只有在真正需要时才计算和存储visitorId
。
function makeLazyGetResult(components: BuiltinComponents): GetResult {
let visitorIdCache: string | undefined
// This function runs very fast, so there is no need to make it lazy
const confidence = getConfidence(components)
// A plain class isn't used because its getters and setters aren't enumerable.
return {
// 一个懒加载的 getter 和 setter,用于访问和设置访客 ID
get visitorId(): string {
if (visitorIdCache === undefined) {
visitorIdCache = hashComponents(this.components)
}
return visitorIdCache
},
set visitorId(visitorId: string) {
visitorIdCache = visitorId
},
confidence, // 计算得到的信心值,表示指纹识别结果的可靠性
components, // 指纹识别过程中使用的组件数据
version, // 版本信息
}
}
该函数创建并返回一个对象:
-
虽然 JavaScript 中可以使用类来封装对象,这样做的一个原因是,类的 getter 和 setter 不能被枚举,而直接使用对象字面量能确保属性是可枚举的,这对于优化某些操作可能是必要的。
-
懒加载的
visitorId
,用于访问和设置访客 ID:- getter:当访问
visitorId
时,首先检查visitorIdCache
是否已经缓存了该值。如果未缓存,则调用hashComponents(this.components)
计算并缓存结果。 - setter:允许直接设置
visitorIdCache
的值。
- getter:当访问