安全Top10 https://cheatsheetseries.owasp.org/IndexTopTen.html
安全审查--跨站请求伪造--Fetch Metadata防护模式
摘要:从小白开始逐层讲解Fetch Metadata
一、从一个现代浏览器的困惑说起
1.1 新时代的安全挑战
想象一下这个场景:你正在开发一个现代化的电商网站,使用了最新的前端框架和API设计。有一天,产品经理找到你说:
"我们的安全团队报告说,虽然我们已经有了CSRF防护,但在移动端和一些现代浏览器上还是可能存在风险。而且我们不想增加太多复杂性,有没有更简单的方法?"
你心想:"都已经2024年了,难道浏览器本身不能提供一些保护机制吗?"
答案是:能!而且比你想象的还要强大!
1.2 浏览器的"自报家门"机制
现代浏览器实际上会给服务器发送一些"身份信息",告诉服务器这个请求是从哪里来的,想要做什么。这就是Fetch Metadata机制。
一个形象的比喻:
想象一下银行的安全系统:
传统方式:
客户进门 → 银行问:"你是谁?" → 客户回答:"我是张三" → 银行核实
↓
问题:任何人都可以说"我是张三"
Fetch Metadata方式:
客户进门 → 银行问:"你是谁?从哪里来?要办什么业务?"
↓
客户回答:"我是张三,从正门进来,要办理转账业务"
↓
银行验证:张三确实是从正门进来的,办理的业务类型也合理
**这就是Fetch Metadata的核心思想:**不仅要知道"你是谁",还要知道"你是怎么来的,想干什么"。
1.3 理解请求的"元数据"
每个HTTP请求都会附带一些"元数据",描述这个请求的背景:
# 这些头是浏览器自动添加的,开发者无法伪造
Sec-Fetch-Site: cross-site # 我是从别的网站来的
Sec-Fetch-Mode: cors # 我是一个跨域请求
Sec-Fetch-Dest: empty # 我的目标不是特定资源
这相当于浏览器在向服务器"汇报":
-
"这个请求是从什么类型的网站发起的"
-
"这个请求是什么模式"
-
"这个请求想要获取什么类型的资源"
二、深入理解Fetch Metadata的工作原理
2.1 三大核心请求头详解
Sec-Fetch-Site:请求的"出身"
这个头告诉我们请求发起的网站类型:
// 不同来源的示例
console.log('请求来源分析:');
console.log('1. https://app.example.com → https://api.example.com');
console.log(' Sec-Fetch-Site: same-site (同一站点)');
console.log('2. https://app.example.com → https://other.com');
console.log(' Sec-Fetch-Site: cross-site (跨站点)');
console.log('3. 用户直接在地址栏输入URL');
console.log(' Sec-Fetch-Site: none (直接访问)');
console.log('4. https://sub.example.com → https://example.com');
console.log(' Sec-Fetch-Site: same-site (子域名也算同站)');
四种可能的值:
- same-origin:完全同源(协议、域名、端口都相同)
- same-site:同站点(可能是不同子域名)
- cross-site:跨站点(完全不同的域名)
- none:用户直接发起(地址栏输入、书签等)
Sec-Fetch-Mode:请求的"方式"
这个头描述请求是如何发起的:
// 不同模式的请求
console.log('请求模式分析:');
console.log('1. 用户点击链接:<a href="...">');
console.log(' Sec-Fetch-Mode: navigate (导航模式)');
console.log('2. fetch("https://api.example.com/data")');
console.log(' Sec-Fetch-Mode: cors (跨域模式)');
console.log('3. <img src="https://example.com/image.jpg">');
console.log(' Sec-Fetch-Mode: no-cors (图片加载模式)');
console.log('4. 内部API调用:fetch("/api/users")');
console.log(' Sec-Fetch-Mode: same-origin (同源模式)');
Sec-Fetch-Dest:请求的"目标"
这个头说明请求想要获取什么类型的资源:
// 不同目标的请求
console.log('请求目标分析:');
console.log('1. fetch("/api/users")');
console.log(' Sec-Fetch-Dest: empty (不明确的资源类型)');
console.log('2. <iframe src="...">');
console.log(' Sec-Fetch-Dest: iframe (iframe内容)');
console.log('3. <script src="...">');
console.log(' Sec-Fetch-Dest: script (JavaScript文件)');
console.log('4. <img src="...">');
console.log(' Sec-Fetch-Dest: image (图片资源)');
2.2 浏览器如何"自动"添加这些头
关键在于:这些头是浏览器自动添加的,JavaScript无法伪造!
<!-- 恶意网站的代码 -->
<script>
// 攻击者尝试伪造请求头
fetch('https://bank.com/api/transfer', {
method: 'POST',
headers: {
// 伪造攻击者的"梦想"
'Sec-Fetch-Site': 'same-origin', // 妄图伪装成同源请求
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Dest': 'empty',
'Content-Type': 'application/json'
},
body: JSON.stringify({ to: 'hacker', amount: 1000 })
});
</script>
实际发生的情况:
POST /api/transfer HTTP/1.1
Host: bank.com
Content-Type: application/json
# 浏览器忽略攻击者的伪造,添加真实信息
Sec-Fetch-Site: cross-site # 真相:这是跨站请求!
Sec-Feach-Mode: cors # 这是CORS请求
Sec-Fetch-Dest: empty # 目标不明确
Cookie: session_id=abc123 # 自动携带银行Cookie
银行服务器一看:
"等等!这个请求说是从same-origin来的,但Sec-Fetch-Site显示是cross-site!
这不就是CSRF攻击吗?拒绝!"
三、从零开始实现Fetch Metadata防护
3.1 最基础的实现
让我们从最简单的防护开始:
// 基础的Fetch Metadata防护中间件
const basicFetchMetadataProtection = (req, res, next) => {
// GET请求通常安全,跳过检查
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
// 获取Fetch Metadata头
const fetchSite = req.headers['sec-fetch-site'];
const fetchMode = req.headers['sec-fetch-mode'];
const fetchDest = req.headers['sec-fetch-dest'];
// 简单规则:拒绝跨站点的POST请求
if (fetchSite === 'cross-site' && req.method !== 'GET') {
return res.status(403).json({
error: 'CSRF防护:跨站点请求被拒绝',
code: 'CROSS_SITE_REQUEST_BLOCKED'
});
}
next();
};
// 应用中间件
app.use('/api', basicFetchMetadataProtection);
这个简单的实现已经能防护大部分CSRF攻击了!
3.2 更细致的规则制定
现在让我们制定更精确的防护规则:
const advancedFetchMetadataProtection = (req, res, next) => {
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
const fetchSite = req.headers['sec-fetch-site'];
const fetchMode = req.headers['sec-fetch-mode'];
const fetchDest = req.headers['sec-fetch-dest'];
// 规则1:检查请求来源
if (fetchSite === 'cross-site') {
// 跨站点请求只允许特定情况
if (!isAllowedCrossSiteRequest(req, fetchMode, fetchDest)) {
return res.status(403).json({
error: '跨站点请求不被允许',
code: 'CROSS_SITE_NOT_ALLOWED',
details: {
fetchSite,
fetchMode,
fetchDest,
method: req.method
}
});
}
}
// 规则2:检查请求逻辑是否合理
if (fetchMode === 'navigate' && req.method !== 'GET') {
// 导航请求通常是GET方法
return res.status(403).json({
error: '可疑的导航请求',
code: 'SUSPICIOUS_NAVIGATE_REQUEST'
});
}
// 规则3:检查请求目标是否合理
if (fetchDest === 'script' && req.method !== 'GET') {
// 脚本资源只能通过GET获取
return res.status(403).json({
error: '脚本资源请求方法错误',
code: 'INVALID_SCRIPT_REQUEST'
});
}
next();
};
// 辅助函数:判断是否允许跨站点请求
function isAllowedCrossSiteRequest(req, fetchMode, fetchDest) {
// 允许表单提交(用户主动操作)
if (fetchMode === 'navigate' && fetchDest === 'document') {
return true;
}
// 允许公开API的CORS请求
const publicApis = ['/api/public/', '/api/webhooks/'];
if (publicApis.some(api => req.path.startsWith(api)) && fetchMode === 'cors') {
return true;
}
return false;
}
3.3 分层防护策略
不同的API端点需要不同的防护级别:
// 分层防护配置
const protectionLevels = {
// 最低级别:公开API
public: {
paths: ['/api/public/', '/api/docs/', '/api/health'],
rules: [
'allow-cross-site-cors',
'basic-validation'
]
},
// 中等级别:普通用户API
user: {
paths: ['/api/user/', '/api/profile/'],
rules: [
'block-cross-site-requests',
'validate-navigation-intent'
]
},
// 最高级别:敏感管理API
admin: {
paths: ['/api/admin/', '/api/delete/', '/api/transfer/'],
rules: [
'require-same-origin',
'strict-validation',
'enhanced-logging'
]
}
};
const layeredProtection = (req, res, next) => {
// 确定请求的防护级别
const level = determineProtectionLevel(req.path);
// 应用对应级别的规则
applyProtectionRules(req, res, level, () => {
next();
});
};
function determineProtectionLevel(path) {
for (const [levelName, config] of Object.entries(protectionLevels)) {
if (config.paths.some(p => path.startsWith(p))) {
return levelName;
}
}
return 'user'; // 默认级别
}
四、进阶实现:生产级Fetch Metadata防护
4.1 智能规则引擎
让我们创建一个灵活的规则引擎:
class FetchMetadataRuleEngine {
constructor() {
this.rules = new Map();
this.loadDefaultRules();
}
loadDefaultRules() {
// 规则:阻止跨站点的状态修改请求
this.rules.set('block-cross-site-state-change', {
name: '阻止跨站点状态修改',
description: '阻止来自跨站点的POST、PUT、DELETE请求',
priority: 100, // 高优先级
condition: (req, metadata) => {
const stateChangeMethods = ['POST', 'PUT', 'DELETE', 'PATCH'];
return metadata.fetchSite === 'cross-site' &&
stateChangeMethods.includes(req.method);
},
action: 'block',
message: '跨站点的状态修改请求被拒绝'
});
// 规则:允许合法的表单提交
this.rules.set('allow-form-submission', {
name: '允许表单提交',
description: '允许用户主动发起的表单提交',
priority: 90,
condition: (req, metadata) => {
return metadata.fetchMode === 'navigate' &&
metadata.fetchDest === 'document';
},
action: 'allow',
message: '合法的表单提交'
});
// 规则:检查API请求的合理性
this.rules.set('validate-api-request', {
name: '验证API请求合理性',
description: '检查API请求的目标和方法是否匹配',
priority: 80,
condition: (req, metadata) => {
const isApiRequest = req.path.startsWith('/api/');
const isJsonRequest = req.headers['content-type']?.includes('application/json');
return isApiRequest && isJsonRequest &&
metadata.fetchDest === 'empty' &&
metadata.fetchMode === 'cors';
},
action: 'allow',
message: '合法的API请求'
});
}
evaluate(req, metadata) {
// 按优先级排序规则
const sortedRules = Array.from(this.rules.entries())
.sort(([, a], [, b]) => b.priority - a.priority);
for (const [ruleName, rule] of sortedRules) {
if (rule.condition(req, metadata)) {
return {
ruleName,
matched: true,
action: rule.action,
message: rule.message,
priority: rule.priority
};
}
}
return {
matched: false,
action: 'allow', // 默认允许
message: '没有匹配的规则'
};
}
addCustomRule(name, rule) {
this.rules.set(name, rule);
console.log(`新增自定义规则: ${rule.name}`);
}
}
// 使用规则引擎
const ruleEngine = new FetchMetadataRuleEngine();
const intelligentProtection = (req, res, next) => {
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
const metadata = extractMetadata(req);
const result = ruleEngine.evaluate(req, metadata);
if (result.action === 'block') {
logSecurityEvent('REQUEST_BLOCKED', req, metadata, result);
return res.status(403).json({
error: result.message,
code: 'SECURITY_VIOLATION',
rule: result.ruleName
});
}
logSecurityEvent('REQUEST_ALLOWED', req, metadata, result);
next();
};
function extractMetadata(req) {
return {
fetchSite: req.headers['sec-fetch-site'],
fetchMode: req.headers['sec-fetch-mode'],
fetchDest: req.headers['sec-fetch-dest'],
fetchUser: req.headers['sec-fetch-user'],
// 检查是否有完整的Fetch Metadata
isComplete: !!(req.headers['sec-fetch-site'] &&
req.headers['sec-fetch-mode'] &&
req.headers['sec-fetch-dest'])
};
}
4.2 安全事件监控和告警
生产环境需要完善的监控系统:
class SecurityMonitor {
constructor() {
this.events = [];
this.alerts = [];
this.thresholds = {
suspiciousRate: 0.05, // 5%的可疑请求率
blockRate: 0.1, // 10%的拦截率
attackThreshold: 10 // 每分钟10次攻击
};
this.startMonitoring();
}
recordSecurityEvent(type, req, metadata, result) {
const event = {
type,
timestamp: new Date().toISOString(),
ip: req.ip,
userAgent: req.headers['user-agent'],
method: req.method,
url: req.url,
referer: req.headers.referer,
metadata,
result
};
this.events.push(event);
// 只保留最近1000个事件
if (this.events.length > 1000) {
this.events.shift();
}
// 实时分析
this.analyzeEvent(event);
}
analyzeEvent(event) {
// 检查可疑模式
if (this.isSuspiciousEvent(event)) {
this.handleSuspiciousEvent(event);
}
// 检查攻击模式
if (this.isAttackPattern(event)) {
this.handleAttackEvent(event);
}
// 检查异常模式
if (this.isAnomalousPattern(event)) {
this.handleAnomalousEvent(event);
}
}
isSuspiciousEvent(event) {
return (
!event.metadata.isComplete || // 缺少Metadata
event.metadata.fetchSite === 'cross-site' || // 跨站点请求
this.hasSuspiciousUserAgent(event.userAgent) || // 可疑的User-Agent
this.isHighRiskIp(event.ip) // 高风险IP
);
}
isAttackPattern(event) {
// 检查是否是明显的攻击模式
const recentEvents = this.getRecentEvents(event.ip, 60000); // 最近1分钟
return (
recentEvents.filter(e => e.type === 'REQUEST_BLOCKED').length > 5 ||
event.result?.ruleName === 'block-cross-site-state-change'
);
}
isAnomalousPattern(event) {
// 检查异常模式
const recentEvents = this.getRecentEvents(event.ip, 3600000); // 最近1小时
// 用户突然改变行为模式
const uniqueUrls = new Set(recentEvents.map(e => e.url)).size;
const uniqueMethods = new Set(recentEvents.map(e => e.method)).size;
return uniqueUrls > 50 || uniqueMethods > 5;
}
handleSuspiciousEvent(event) {
console.warn('[可疑事件]', {
ip: event.ip,
userAgent: event.userAgent,
url: event.url,
metadata: event.metadata
});
// 可以发送到SIEM系统
this.sendToSIEM('SUSPICIOUS', event);
}
handleAttackEvent(event) {
console.error('[攻击事件]', {
ip: event.ip,
attackType: event.result?.ruleName,
url: event.url,
timestamp: event.timestamp
});
// 立即告警
this.sendImmediateAlert('ATTACK_DETECTED', event);
// 可以临时封禁IP
this.temporaryBlockIp(event.ip, 3600); // 封禁1小时
}
handleAnomalousEvent(event) {
console.info('[异常模式]', {
ip: event.ip,
pattern: 'behavioral_change',
timestamp: event.timestamp
});
this.sendToAnalytics('ANOMALY', event);
}
generateSecurityReport() {
const now = new Date();
const last24h = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const recentEvents = this.events.filter(e => new Date(e.timestamp) > last24h);
const report = {
period: 'last_24_hours',
totalRequests: recentEvents.length,
blockedRequests: recentEvents.filter(e => e.type === 'REQUEST_BLOCKED').length,
suspiciousEvents: recentEvents.filter(e => e.type === 'SUSPICIOUS_EVENT').length,
attackEvents: recentEvents.filter(e => e.type === 'ATTACK_EVENT').length,
topAttackerIps: this.getTopAttackerIps(recentEvents),
mostAttackedEndpoints: this.getMostAttackedEndpoints(recentEvents),
securityScore: this.calculateSecurityScore(recentEvents)
};
return report;
}
calculateSecurityScore(events) {
const total = events.length;
const blocked = events.filter(e => e.type === 'REQUEST_BLOCKED').length;
const suspicious = events.filter(e => e.type === 'SUSPICIOUS_EVENT').length;
// 安全评分算法
const blockRate = total > 0 ? blocked / total : 0;
const suspiciousRate = total > 0 ? suspicious / total : 0;
let score = 100;
score -= blockRate * 500; // 拦截率影响
score -= suspiciousRate * 200; // 可疑率影响
return Math.max(0, Math.round(score));
}
}
// 创建监控实例
const securityMonitor = new SecurityMonitor();
function logSecurityEvent(type, req, metadata, result) {
securityMonitor.recordSecurityEvent(type, req, metadata, result);
}
4.3 自适应防护策略
根据实时威胁情报调整防护策略:
class AdaptiveProtection {
constructor() {
this.threatLevel = 'normal'; // low, normal, high, critical
this.protectionLevels = {
low: {
strictness: 0.3,
logging: 'minimal',
blocking: 'selective'
},
normal: {
strictness: 0.6,
logging: 'standard',
blocking: 'aggressive'
},
high: {
strictness: 0.8,
logging: 'verbose',
blocking: 'strict'
},
critical: {
strictness: 1.0,
logging: 'exhaustive',
blocking: 'maximum'
}
};
this.startThreatAssessment();
}
startThreatAssessment() {
// 每5分钟评估一次威胁等级
setInterval(() => {
this.assessThreatLevel();
this.adjustProtection();
}, 5 * 60 * 1000);
}
assessThreatLevel() {
const report = securityMonitor.generateSecurityReport();
// 威胁评估算法
const attackRate = report.attackEvents / report.totalRequests;
const blockRate = report.blockedRequests / report.totalRequests;
if (attackRate > 0.1 || report.attackEvents > 100) {
this.threatLevel = 'critical';
} else if (attackRate > 0.05 || report.attackEvents > 50) {
this.threatLevel = 'high';
} else if (attackRate > 0.01 || blockRate > 0.1) {
this.threatLevel = 'normal';
} else {
this.threatLevel = 'low';
}
console.log(`威胁等级调整为: ${this.threatLevel}`, report);
}
adjustProtection() {
const config = this.protectionLevels[this.threatLevel];
// 动态调整规则引擎的严格程度
ruleEngine.rules.forEach((rule, name) => {
if (config.strictness > 0.7) {
// 高严格性:启用更严格的条件
this.enhanceRuleStrictness(rule);
} else if (config.strictness < 0.5) {
// 低严格性:放宽一些条件
this.relaxRuleStrictness(rule);
}
});
// 发送通知
this.notifyThreatLevelChange(this.threatLevel, config);
}
getMiddleware() {
return (req, res, next) => {
// 根据当前威胁等级调整行为
const config = this.protectionLevels[this.threatLevel];
if (this.threatLevel === 'critical') {
// 关键威胁等级:额外验证
this.applyCriticalProtection(req, res, next);
} else {
// 正常流程
intelligentProtection(req, res, next);
}
};
}
applyCriticalProtection(req, res, next) {
// 关键威胁时的额外保护措施
console.warn(`[关键威胁] 对请求 ${req.method} ${req.path} 应用严格验证`);
// 可以添加额外的验证步骤
this.performAdditionalValidation(req)
.then(() => next())
.catch(error => {
res.status(403).json({
error: '威胁等级过高,请求被拒绝',
code: 'CRITICAL_THREAT_PROTECTION'
});
});
}
}
const adaptiveProtection = new AdaptiveProtection();
五、实战应用:处理复杂场景
5.1 浏览器兼容性处理
不是所有浏览器都支持Fetch Metadata:
class BrowserCompatibilityManager {
static async detectSupport() {
return new Promise((resolve) => {
// 创建测试请求检测支持
const testRequest = new XMLHttpRequest();
testRequest.open('HEAD', '/api/test-metadata', true);
testRequest.onreadystatechange = function() {
if (this.readyState === 2) { // HEADERS_RECEIVED
const hasMetadata = !!(
this.getResponseHeader('Sec-Fetch-Site') ||
this.getResponseHeader('Sec-Fetch-Mode') ||
this.getResponseHeader('Sec-Fetch-Dest')
);
resolve({
supported: hasMetadata,
browser: this.detectBrowser(),
version: this.getBrowserVersion()
});
}
};
testRequest.onerror = () => resolve({ supported: false });
testRequest.send();
});
}
static createCompatibleProtection() {
return async (req, res, next) => {
// 检查浏览器支持
const support = await this.detectSupport();
if (support.supported) {
// 现代浏览器:使用Fetch Metadata
return intelligentProtection(req, res, next);
} else {
// 旧浏览器:使用其他CSRF防护
console.warn(`浏览器不支持Fetch Metadata,使用降级方案: ${support.browser}`);
return fallbackProtection(req, res, next);
}
};
}
static detectBrowser() {
const ua = navigator.userAgent;
if (ua.includes('Chrome') && !ua.includes('Edg')) return 'chrome';
if (ua.includes('Firefox')) return 'firefox';
if (ua.includes('Safari') && !ua.includes('Chrome')) return 'safari';
if (ua.includes('Edg')) return 'edge';
if (ua.includes('Opera') || ua.includes('OPR')) return 'opera';
return 'unknown';
}
static getBrowserVersion() {
const ua = navigator.userAgent;
const match = ua.match(/(Chrome|Firefox|Safari|Edg|Opera)\/(\d+)/);
return match ? match[2] : 'unknown';
}
}
// 使用兼容性管理器
app.use('/api', await BrowserCompatibilityManager.createCompatibleProtection());
5.2 多环境配置
const environments = {
development: {
protection: {
mode: 'basic',
logging: 'debug',
strictness: 0.3
},
monitoring: {
enabled: true,
alertThreshold: 100
}
},
staging: {
protection: {
mode: 'advanced',
logging: 'verbose',
strictness: 0.7
},
monitoring: {
enabled: true,
alertThreshold: 20
}
},
production: {
protection: {
mode: 'adaptive',
logging: 'security',
strictness: 0.8
},
monitoring: {
enabled: true,
alertThreshold: 10
}
}
};
const env = process.env.NODE_ENV || 'development';
const config = environments[env];
// 根据环境应用不同的配置
function createEnvironmentSpecificProtection() {
switch (config.protection.mode) {
case 'basic':
return basicFetchMetadataProtection;
case 'advanced':
return intelligentProtection;
case 'adaptive':
return adaptiveProtection.getMiddleware();
default:
return basicFetchMetadataProtection;
}
}
app.use('/api', createEnvironmentSpecificProtection());
六、常见问题和解决方案
6.1 "为什么有些请求被误拦截了?"
问题:用户反馈正常操作被拦截
原因分析和解决方案:
// 原因1:用户使用旧浏览器
// 解决:提供降级方案
const fallbackProtection = (req, res, next) => {
// 对于不支持Fetch Metadata的浏览器,使用传统CSRF防护
if (!req.headers['sec-fetch-site']) {
return traditionalCsrfProtection(req, res, next);
}
next();
};
// 原因2:某些合法的跨域请求被拦截
// 解决:精确配置例外规则
const whitelistRules = {
allowedCrossSiteOrigins: [
'https://partner.example.com',
'https://payment.gateway.com'
],
allowedCrossSitePaths: [
'/api/webhooks/',
'/api/callbacks/',
'/api/public/'
]
};
const enhancedProtection = (req, res, next) => {
const origin = req.headers.origin;
const path = req.path;
// 检查是否在白名单中
if (whitelistRules.allowedCrossSiteOrigins.includes(origin) ||
whitelistRules.allowedCrossSitePaths.some(p => path.startsWith(p))) {
return next();
}
// 继续正常验证流程
intelligentProtection(req, res, next);
};
6.2 "如何平衡安全性和用户体验?"
策略:动态调整防护严格程度
class UserExperienceOptimizedProtection {
constructor() {
this.userTrustScores = new Map(); // 用户信任评分
}
middleware() {
return (req, res, next) => {
const userId = req.user?.id;
const trustScore = this.getUserTrustScore(userId);
// 高信任度用户:宽松的验证
if (trustScore > 0.8) {
return this.applyLenientProtection(req, res, next);
}
// 低信任度用户:严格验证
if (trustScore < 0.3) {
return this.applyStrictProtection(req, res, next);
}
// 普通用户:标准验证
intelligentProtection(req, res, next);
};
}
getUserTrustScore(userId) {
if (!userId) return 0.5; // 未登录用户的默认信任度
const score = this.userTrustScores.get(userId) || 0.5;
return score;
}
updateUserTrustScore(userId, action) {
let score = this.getUserTrustScore(userId);
switch (action) {
case 'successful_login':
score += 0.1;
break;
case 'security_violation':
score -= 0.2;
break;
case 'long_term_user':
score += 0.05;
break;
}
score = Math.max(0, Math.min(1, score));
this.userTrustScores.set(userId, score);
}
}
6.3 "Fetch Metadata能完全替代其他CSRF防护吗?"
答案:不能完全替代,建议作为深度防御的一部分
// 推荐的混合防护策略
class DefenseInDepthProtection {
constructor() {
this.layers = [
{ name: 'fetch-metadata', enabled: true, priority: 1 },
{ name: 'rate-limiting', enabled: true, priority: 2 },
{ name: 'same-site-cookies', enabled: true, priority: 3 },
{ name: 'csrf-tokens', enabled: false, priority: 4 } // 仅在高风险时启用
];
}
middleware() {
return async (req, res, next) => {
let blocked = false;
let blockReason = '';
// 逐层检查
for (const layer of this.layers.filter(l => l.enabled)) {
const result = await this.executeLayer(layer.name, req);
if (result.blocked) {
blocked = true;
blockReason = result.reason;
break;
}
}
if (blocked) {
return res.status(403).json({
error: '多层安全防护拒绝请求',
reason: blockReason,
layers: this.layers.map(l => l.name)
});
}
next();
};
}
async executeLayer(layerName, req) {
switch (layerName) {
case 'fetch-metadata':
return this.checkFetchMetadata(req);
case 'rate-limiting':
return this.checkRateLimiting(req);
case 'same-site-cookies':
return this.checkSameSite(req);
case 'csrf-tokens':
return this.checkCsrfToken(req);
default:
return { blocked: false };
}
}
}
七、总结:Fetch Metadata的优势和局限
优势:
-
零实现成本:浏览器原生支持,无需复杂的token管理
-
高性能:仅需HTTP头解析,无额外计算开销
-
无状态:天然支持分布式和微服务架构
-
现代安全:基于浏览器的最新安全特性
局限性:
-
浏览器依赖:需要现代浏览器支持
-
信息有限:只能判断请求的"出身",无法完全确定用户意图
-
配置复杂:需要精确的规则制定和调整
-
不能单独使用:建议作为深度防御的一部分
适用场景:
-
现代Web应用(用户使用较新浏览器)
-
高并发API服务
-
微服务架构
-
对性能要求极高的场景
-
需要快速部署安全防护的项目
Fetch Metadata代表了Web安全的未来方向,它让我们能够利用浏览器本身的安全能力,构建更智能、更高效的防护系统。虽然不能完全替代传统的CSRF防护,但作为现代Web安全架构的重要组成部分,它正在发挥着越来越重要的作用。
安全审查--跨站请求伪造--Fetch Metadata防护模式