一、Client Hints 指纹介绍
客户端提示是一组 HTTP 标头和一个 JavaScript API,允许 Web 浏览器将有关客户端设备和浏览器的详细信息发送到 Web 服务器。它们旨在成为 User-Agent 的继任者,并为 Web 服务器提供了一种标准化方法,以便为客户端优化内容,而无需依赖不可靠的基于 User-Agent 字符串的检测或浏览器指纹识别技术。
已知客户端提示列表
Sec-CH-UA指示用户代理的品牌和版本
Sec-CH-UA-Full-Version表示用户代理的完整版本(已弃用,替换为 Sec-CH-UA-Full-Version-List
Sec-CH-UA-Full-Version-List表示其品牌列表中每个品牌的完整版本。
Sec-CH-UA-Platform指示给定用户代理正在执行的平台。
Sec-CH-UA-Platform-Version指示给定用户代理正在执行的平台版本。
Sec-CH-UA-Arch表示给定用户代理正在执行的平台的体系结构。
Sec-CH-UA-Bitness指示给定用户代理正在执行的平台体系结构的位数。
Sec-CH-UA-WoW64用于检测用户代理二进制文件是否在 64 位 Windows 上以 32 位模式运行。
Sec-CH-UA-Model表示正在执行给定用户代理的设备。
Sec-CH-UA-Mobile用于检测用户代理是否更喜欢“移动”用户体验。
Sec-CH-UA-Form-Factors指示设备的外形规格,以前在 User-Agent 字符串中表示为 <deviceCompat> 令牌。
Sec-CH-Lang(或 Lang)表示用户的语言首选项。
Sec-CH-Save-Data(或 Save-Data)表示用户代理减少数据使用量的首选项。
Sec-CH-Width向服务器提供图像的布局宽度。
Sec-CH-Viewport-Width(或 Viewport-Width)是用户视区的宽度(以 CSS 像素为单位)。
Sec-CH-Viewport-Height表示用户代理的当前视区高度。
Sec-CH-DPR(或 DPR)报告用户屏幕的物理像素与 CSS 像素的比率。
Sec-CH-Device-Memory(或 Device-Memory)显示设备当前大约有多少内存(以 GiB 为单位)。由于此信息可用于识别用户,因此 Device-Memory 的值故意很粗糙。有效值为 0.25、0.5、1、2、4 和 8。
Sec-CH-RTT(或 RTT)提供应用程序层的大致往返时间(以毫秒为单位)。RTT 与传输层 RTT 的不同之处在于它包括服务器处理时间。RTT 的值四舍五入到最接近的 25 毫秒,以防止指纹识别。
Sec-CH-Downlink(或下行链路)它以每秒兆位 (Mbps) 表示,显示用户连接的大致下行速度。该值四舍五入到最接近的倍数,即每秒 25 KB。因为再次执行指纹识别。
Sec-CH-ECT(或 ECT)代表 Valid Connection Type(有效连接类型)。它的值是连接类型枚举列表之一,每个类型描述指定范围内具有 RTT 和下行链路值的连接。有效的 ECT 值为 4g、3g、2g 和 slow-2g。
Sec-CH-Prefers-Color-Scheme表示用户最喜欢的配色方案。
Sec-CH-Prefers-Reduced-Motion用于检测用户是否要求系统最小化其使用的动画或运动量。
Sec-CH-Prefers-Reduced-Transparency用于检测用户是否要求系统最小化其使用的透明或半透明图层效果的数量。
Sec-CH-Prefers-Contrast用于检测用户是否请求以更高(或更低)的对比度呈现 Web 内容。
Sec-CH-Forced-Colors用于检测用户代理是否启用了强制颜色模式,在该模式下,它在页面上强制执行有限的用户选择的调色板。
二、浏览器client hints截图:
三、 client hints源码定义:
services\network\public\cpp\client_hints.cc
services\network\public\cpp\client_hints.h
namespace network {
const char kPrefersColorSchemeDark[] = "dark";
const char kPrefersColorSchemeLight[] = "light";
const char kPrefersReducedMotionNoPreference[] = "no-preference";
const char kPrefersReducedMotionReduce[] = "reduce";
const char kPrefersReducedTransparencyNoPreference[] = "no-preference";
const char kPrefersReducedTransparencyReduce[] = "reduce";
const char* const kWebEffectiveConnectionTypeMapping[] = {
"4g" /* Unknown */, "4g" /* Offline */, "slow-2g" /* Slow 2G */,
"2g" /* 2G */, "3g" /* 3G */, "4g" /* 4G */
};
const size_t kWebEffectiveConnectionTypeMappingCount =
std::size(kWebEffectiveConnectionTypeMapping);
ClientHintToNameMap MakeClientHintToNameMap() {
return {
{network::mojom::WebClientHintsType::kDeviceMemory_DEPRECATED,
"device-memory"},
{network::mojom::WebClientHintsType::kDpr_DEPRECATED, "dpr"},
{network::mojom::WebClientHintsType::kResourceWidth_DEPRECATED, "width"},
{network::mojom::WebClientHintsType::kViewportWidth_DEPRECATED,
"viewport-width"},
{network::mojom::WebClientHintsType::kRtt_DEPRECATED, "rtt"},
{network::mojom::WebClientHintsType::kDownlink_DEPRECATED, "downlink"},
{network::mojom::WebClientHintsType::kEct_DEPRECATED, "ect"},
{network::mojom::WebClientHintsType::kUA, "sec-ch-ua"},
{network::mojom::WebClientHintsType::kUAArch, "sec-ch-ua-arch"},
{network::mojom::WebClientHintsType::kUAPlatform, "sec-ch-ua-platform"},
{network::mojom::WebClientHintsType::kUAModel, "sec-ch-ua-model"},
{network::mojom::WebClientHintsType::kUAMobile, "sec-ch-ua-mobile"},
{network::mojom::WebClientHintsType::kUAFullVersion,
"sec-ch-ua-full-version"},
{network::mojom::WebClientHintsType::kUAPlatformVersion,
"sec-ch-ua-platform-version"},
{network::mojom::WebClientHintsType::kPrefersColorScheme,
"sec-ch-prefers-color-scheme"},
{network::mojom::WebClientHintsType::kUABitness, "sec-ch-ua-bitness"},
{network::mojom::WebClientHintsType::kViewportHeight,
"sec-ch-viewport-height"},
{network::mojom::WebClientHintsType::kDeviceMemory,
"sec-ch-device-memory"},
{network::mojom::WebClientHintsType::kDpr, "sec-ch-dpr"},
{network::mojom::WebClientHintsType::kResourceWidth, "sec-ch-width"},
{network::mojom::WebClientHintsType::kViewportWidth,
"sec-ch-viewport-width"},
{network::mojom::WebClientHintsType::kUAFullVersionList,
"sec-ch-ua-full-version-list"},
{network::mojom::WebClientHintsType::kUAWoW64, "sec-ch-ua-wow64"},
{network::mojom::WebClientHintsType::kSaveData, "save-data"},
{network::mojom::WebClientHintsType::kPrefersReducedMotion,
"sec-ch-prefers-reduced-motion"},
{network::mojom::WebClientHintsType::kUAFormFactor,
"sec-ch-ua-form-factor"},
{network::mojom::WebClientHintsType::kPrefersReducedTransparency,
"sec-ch-prefers-reduced-transparency"},
};
}
const ClientHintToNameMap& GetClientHintToNameMap() {
static const base::NoDestructor<ClientHintToNameMap> map(
MakeClientHintToNameMap());
return *map;
}
namespace {
struct ClientHintNameComparator {
bool operator()(const std::string& lhs, const std::string& rhs) const {
return base::CompareCaseInsensitiveASCII(lhs, rhs) < 0;
}
};
using DecodeMap = base::flat_map<std::string,
network::mojom::WebClientHintsType,
ClientHintNameComparator>;
DecodeMap MakeDecodeMap() {
DecodeMap result;
for (const auto& elem : network::GetClientHintToNameMap()) {
const auto& type = elem.first;
const auto& header = elem.second;
result.insert(std::make_pair(header, type));
}
return result;
}
const DecodeMap& GetDecodeMap() {
static const base::NoDestructor<DecodeMap> decode_map(MakeDecodeMap());
return *decode_map;
}
} // namespace
absl::optional<std::vector<network::mojom::WebClientHintsType>>
ParseClientHintsHeader(const std::string& header) {
// Accept-CH is an sh-list of tokens; see:
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-header-structure-19#section-3.1
absl::optional<net::structured_headers::List> maybe_list =
net::structured_headers::ParseList(header);
if (!maybe_list.has_value())
return absl::nullopt;
// Standard validation rules: we want a list of tokens, so this better
// only have tokens (but params are OK!)
for (const auto& list_item : maybe_list.value()) {
// Make sure not a nested list.
if (list_item.member.size() != 1u)
return absl::nullopt;
if (!list_item.member[0].item.is_token())
return absl::nullopt;
}
std::vector<network::mojom::WebClientHintsType> result;
// Now convert those to actual hint enums.
const DecodeMap& decode_map = GetDecodeMap();
for (const auto& list_item : maybe_list.value()) {
const std::string& token_value = list_item.member[0].item.GetString();
auto iter = decode_map.find(token_value);
if (iter != decode_map.end())
result.push_back(iter->second);
} // for list_item
return result;
}
ClientHintToDelegatedThirdPartiesHeader::
ClientHintToDelegatedThirdPartiesHeader() = default;
ClientHintToDelegatedThirdPartiesHeader::
~ClientHintToDelegatedThirdPartiesHeader() = default;
ClientHintToDelegatedThirdPartiesHeader::
ClientHintToDelegatedThirdPartiesHeader(
const ClientHintToDelegatedThirdPartiesHeader&) = default;
const ClientHintToDelegatedThirdPartiesHeader
ParseClientHintToDelegatedThirdPartiesHeader(const std::string& header,
MetaCHType type) {
const DecodeMap& decode_map = GetDecodeMap();
switch (type) {
case MetaCHType::HttpEquivAcceptCH: {
// ParseClientHintsHeader should have been called instead.
NOTREACHED();
return ClientHintToDelegatedThirdPartiesHeader();
}
case MetaCHType::HttpEquivDelegateCH: {
// We're building a scoped down version of
// ParsingContext::ParseFeaturePolicyToIR that supports only client hint
// features.
ClientHintToDelegatedThirdPartiesHeader result;
const auto& entries =
base::SplitString(base::ToLowerASCII(header), ";",
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const auto& entry : entries) {
const auto& components = base::SplitString(
entry, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// Shouldn't be possible given this only processes non-empty parts.
DCHECK(components.size());
auto found_token = decode_map.find(components[0]);
// Bail early if the token is invalid
if (found_token == decode_map.end())
continue;
std::vector<url::Origin> delegates;
for (size_t i = 1; i < components.size(); ++i) {
const GURL gurl = GURL(components[i]);
if (!gurl.is_valid()) {
result.had_invalid_origins = true;
continue;
}
url::Origin origin = url::Origin::Create(gurl);
if (origin.opaque()) {
result.had_invalid_origins = true;
continue;
}
delegates.push_back(origin);
}
result.map.insert(std::make_pair(found_token->second, delegates));
}
return result;
}
}
}
// static
void LogClientHintsPersistenceMetrics(
const base::TimeTicks& persistence_started,
std::size_t hints_stored) {
base::UmaHistogramTimes("ClientHints.StoreLatency",
base::TimeTicks::Now() - persistence_started);
base::UmaHistogramExactLinear("ClientHints.UpdateEventCount", 1, 2);
base::UmaHistogramCounts100("ClientHints.UpdateSize", hints_stored);
}
} // namespace network
四、看下调用堆栈:
五、 client_hints指纹修改总结:
修改sec-ch-ua-platform等内容在client_hints.cc文件里修改即可。