被源网站风控的应对方案之破解相关(二)
写在开篇
扎实的“JS逆向工程”能力&丰富的实战经验 可以让我们快速定位到源网站的风控关键点,但,这种能力需要在较长的时间和反复的实践中获得,并不是一两篇文章就能够说明清楚的。
那么,今天这一章要说点什么呢?
说说Chrome Extension中“请求头的动态设置”吧。毕竟在大部分情况下,获取到 关键请求头部参数 后,我们得设置请求头才能传递给源网站,而且请求头的动态设置是浏览器插件的重要使用场景之一,包括往后的章节也会涉及到。
注,作者当前遇到的大部分情况都是将风控相关的参数信息设置在请求头中的,但也有少数是将参数设置在请求体中的,所以大家在运用中要遵循实际情况哦~
有关请求头设置说明
官方提供了dynamic、session、static三种方案,其中前两种是由js代码主导设置的,而static是配置在插件的manifest.json文件中的。
dynamic
由JS代码执行时主导设置;
由测试实践得出,当Service Worker休眠时,会清除“其设置的请求头更新规则”;
session
由JS代码执行时主导设置;
由官方文档&测试实践得出,session请求头更新规则 会在浏览器关闭、更新时被清除;
static
static是配置在插件的manifest.json文件中的,即,有更新就要升级插件包;
一般,为了业务场景的扩展灵活性,我们会尽量采用dynamic、session方案。技术方案可以采用:在数据库中存储相关请求的请求头设置信息,在适当时机获取并设置,而不是每次有新业务场景的时候去更新插件包。
本文,我们采用 dynamic 方案来阐述。因为,session 的清除时机是在浏览器关闭和更新时,那么,在设置时就必须注意 规则的唯一性 以及 规则的更新时机,比如,原来存储在session中的是A与B,在某天的某一个时刻,需要更新为A+、B+,要怎么做、何时做呢?不仅如此,在需再综合考虑技术成本下,想起来还是挺复杂的。而,dynamic方案本身被插件存储的时效就比较短,那我们完全可以抛弃它的时效特性 & 损失一点点执行效率,在请求之前进行设置&请求完毕之后清除更新规则,以此来达到动态设置的效果:
// 这个函数用于确认新产生的规则ID是否存在于已经设置的更新规则中,因为官方要求设置的每个更新规则的规则ID必须唯一
async function _checkRuleExist(ruleId) {
const rules = await chrome.declarativeNetRequest.getDynamicRules();
if (!rules || !rules.length) {
return false;
}
let _exists = false;
for (let i = 0; i < rules.length; i++) {
if (rules[i].id === ruleId) {
_exists = true;
break;
}
}
return _exists;
}
// 这是产生规则ID的函数,如果循环了10次都有重复,那就是这个请求命里该绝:报错吧~
async function _getUniqueRuleId() {
let _loopIndex = 0;
let _ruleId;
while (_loopIndex < 10) {
_ruleId = parseInt(Math.random() * 1000000);
const _checkExists = await _checkRuleExist(_ruleId);
if (!_checkExists) {
break;
}
++_loopIndex;
}
return _ruleId;
}
const _NEED_UPDATE_SET_HEADER_LIST = []; // 这是个需要设置的请求头数组,是本章的另一个重点,留个悬念,本章下一小节揭晓答案~
async function _fetchApi(options) {
// 此处,我把所有需要的请求头都汇集在headers参数中传进来了,实际运用时也可以分开传递
const { headers, url } = options;
const _hKeys = Object.keys(headers);
const _fetchHeaders = {}; // 请求时需要设置在fetch中的头部,需要除去动态设置的
const _updateHeaders = []; // 需要设置更新规则
for (const _hkey of _hKeys) {
if (_NEED_UPDATE_SET_HEADER_LIST.includes(_hkey)) {
_updateHeaders.push({
header: _hkey,
operation: 'set',
value: headers[_hkey],
});
} else {
_fetchHeaders[_hkey] = headers[_hkey];
}
}
const _ruleId = await _getUniqueRuleId();
if (!_ruleId) {
throw new Error('ruleId exists');
}
// 设置更新规则
await chrome.declarativeNetRequest.updateDynamicRules({
addRules: [
{
id: _ruleId,
priority: 1,
action: {
type: 'modifyHeaders',
requestHeaders: _updateHeaders,
},
condition: {
urlFilter: url,
},
},
],
});
// 发出请求,请求时,Chrome Extension会根据上面设置的规则,给这个请求设置请求头~
const _result = await fetch(url, {
...options,
headers: _fetchHeaders,
});
// 请求完毕,就把设置的规则清掉~
await _removeDynamicRuleById(_ruleId);
return _result;
}
export async function _removeDynamicRuleById(ruleId) {
if (!ruleId) {
return;
}
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [ruleId],
});
}
另外,虽然本文只提供了dynamic的相关方案,session的设置代码其实是相似的,只不过设计方案更复杂而已:
// 设置更新请求头规则代码如下,只是把updateDynamicRules换成了updateSessionRules;
// 另外,从整体技术方案的角度出发,设置规则的时机不再是请求前,而是插件启动时,确认需要设置;
// 因为,使用session方案,大多情况是要利用其 长效存储 特性;
await chrome.declarativeNetRequest.updateSessionRules({
addRules: [
{
id: _ruleId,
priority: 1,
action: {
type: 'modifyHeaders',
requestHeaders: _updateHeaders,
},
condition: {
urlFilter: url,
},
},
],
});
// 获取请求头更新规则代码如下,只是把getDynamicRules换成getSessionRules而已
await chrome.declarativeNetRequest.getSessionRules();
// 删除请求头更新规则
await chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [ruleId],
});
哪些请求头需特别设置?
看过了设置动态设置请求头的代码后,你一定很好奇,哪些请求头需要被设置呢?
根据以往经验,一般这几个请求头需要设置,因为这几个请求头不仅与风控管理有关,而且直接设置到fetch头部中也是无效的(后续如果发现了更多需要设置的请求头,我也会及时更新到本文中):
// 神秘面纱揭晓~
const _NEED_UPDATE_SET_HEADER_LIST = ['referer', 'origin', 'sec-ch-ua', 'sec-ch-ua-mobile', 'sec-ch-ua-platform'];
写在最后
好啦,以上就是本章的全部内容啦,让我们来回忆总结下知识点,希望你都能牢记吧~
首先,我们对接了当前Chrome Extension V3版本中有哪些更改请求头的方案;
其次,用核心代码展示了dynamic方案的运用,因为作为实践者,综合下来我更倾向于这个方案;
而后,点明了需要设置的、与风控相关度高的请求头;
作为实践者,虽然我更倾向于dynamic方案,但是如果你的插件面对的网站比较少且能够做好请求分类的话,倒是可以使用session的,毕竟可以大大减少多次动态设置规则的时间&提高执行效率;
最后,今日与君共勉:以匠心、致初心,愿所有人能在这快餐、内卷、浮躁的时代里,找到自己想要为之脚踏实地努力的事情。