乐闻世界logo
搜索文章和话题

面试题手册

企业级应用中如何设计和实现 CSRF 防护架构?

CSRF 防护在企业级应用中需要考虑架构、性能、可扩展性和合规性等多个方面,以确保在复杂的环境中提供有效的安全保护。企业级 CSRF 防护架构1. 集中式 Token 管理// 集中式 Token 服务class CentralizedTokenService { constructor(redisClient) { this.redis = redisClient; this.tokenExpiry = 3600; // 1 小时 } async generateToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const key = `csrf:${userId}:${token}`; await this.redis.setex(key, this.tokenExpiry, '1'); return token; } async validateToken(userId, token) { const key = `csrf:${userId}:${token}`; const exists = await this.redis.exists(key); if (exists) { // 验证成功后删除 Token(一次性使用) await this.redis.del(key); return true; } return false; } async revokeUserTokens(userId) { const pattern = `csrf:${userId}:*`; const keys = await this.redis.keys(pattern); if (keys.length > 0) { await this.redis.del(...keys); } }}2. 分布式环境下的 CSRF 防护// 分布式 CSRF 防护中间件class DistributedCSRFProtection { constructor(tokenService, config) { this.tokenService = tokenService; this.config = config; } middleware() { return async (req, res, next) => { // 跳过安全方法 if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) { return next(); } // 获取用户 ID const userId = this.getUserId(req); if (!userId) { return res.status(401).send('Unauthorized'); } // 验证 Token const token = this.extractToken(req); if (!token) { return res.status(403).send('CSRF Token required'); } const isValid = await this.tokenService.validateToken(userId, token); if (!isValid) { return res.status(403).send('Invalid CSRF Token'); } next(); }; } getUserId(req) { // 从 Session 或 JWT 中获取用户 ID return req.user?.id || req.session?.userId; } extractToken(req) { // 从请求头或请求体中提取 Token return req.headers['x-csrf-token'] || req.body._csrf; }}性能优化策略1. Token 缓存// 使用 Redis 缓存 Tokenclass CachedTokenService { constructor(redisClient, localCache) { this.redis = redisClient; this.localCache = localCache; this.ttl = 3600; } async getToken(userId) { // 首先检查本地缓存 const cachedToken = this.localCache.get(`csrf:${userId}`); if (cachedToken) { return cachedToken; } // 从 Redis 获取 const token = await this.redis.get(`csrf:${userId}`); if (token) { this.localCache.set(`csrf:${userId}`, token, this.ttl); return token; } // 生成新 Token const newToken = crypto.randomBytes(32).toString('hex'); await this.redis.setex(`csrf:${userId}`, this.ttl, newToken); this.localCache.set(`csrf:${userId}`, newToken, this.ttl); return newToken; }}2. 批量 Token 验证// 批量验证多个请求的 Tokenclass BatchTokenValidator { constructor(tokenService) { this.tokenService = tokenService; this.batchSize = 100; } async validateBatch(requests) { const results = []; for (let i = 0; i < requests.length; i += this.batchSize) { const batch = requests.slice(i, i + this.batchSize); const batchResults = await Promise.all( batch.map(req => this.validateSingle(req)) ); results.push(...batchResults); } return results; } async validateSingle(request) { const { userId, token } = request; const isValid = await this.tokenService.validateToken(userId, token); return { userId, token, isValid, timestamp: Date.now() }; }}高可用性设计1. Token 服务冗余// 多个 Token 服务的负载均衡class RedundantTokenService { constructor(services) { this.services = services; this.currentService = 0; } async generateToken(userId) { const service = this.getNextService(); try { return await service.generateToken(userId); } catch (error) { // 尝试下一个服务 return await this.generateTokenWithRetry(userId, 3); } } async generateTokenWithRetry(userId, retries) { for (let i = 0; i < retries; i++) { const service = this.getNextService(); try { return await service.generateToken(userId); } catch (error) { if (i === retries - 1) { throw error; } } } } getNextService() { const service = this.services[this.currentService]; this.currentService = (this.currentService + 1) % this.services.length; return service; }}2. 故障转移// 故障检测和转移class FailoverTokenService { constructor(primaryService, backupService) { this.primary = primaryService; this.backup = backupService; this.isPrimaryHealthy = true; this.healthCheckInterval = 30000; // 30 秒 } startHealthCheck() { setInterval(async () => { try { await this.primary.ping(); this.isPrimaryHealthy = true; } catch (error) { console.error('Primary service unhealthy:', error); this.isPrimaryHealthy = false; } }, this.healthCheckInterval); } async generateToken(userId) { if (this.isPrimaryHealthy) { try { return await this.primary.generateToken(userId); } catch (error) { console.error('Primary service failed, using backup:', error); return await this.backup.generateToken(userId); } } return await this.backup.generateToken(userId); }}合规性和审计1. 审计日志// CSRF 操作审计日志class CSRFAuditLogger { constructor(logService) { this.logService = logService; } async logTokenGeneration(userId, token, metadata) { const auditLog = { event: 'CSRF_TOKEN_GENERATED', userId, tokenHash: this.hashToken(token), timestamp: new Date().toISOString(), metadata: { ip: metadata.ip, userAgent: metadata.userAgent, sessionId: metadata.sessionId } }; await this.logService.log(auditLog); } async logTokenValidation(userId, token, isValid, metadata) { const auditLog = { event: 'CSRF_TOKEN_VALIDATED', userId, tokenHash: this.hashToken(token), isValid, timestamp: new Date().toISOString(), metadata: { ip: metadata.ip, userAgent: metadata.userAgent, sessionId: metadata.sessionId, requestPath: metadata.requestPath } }; await this.logService.log(auditLog); } hashToken(token) { return crypto.createHash('sha256').update(token).digest('hex'); }}2. 合规性报告// 生成合规性报告class ComplianceReportGenerator { constructor(auditLogger) { this.auditLogger = auditLogger; } async generateReport(startDate, endDate) { const logs = await this.auditLogger.getLogs(startDate, endDate); return { summary: { totalTokensGenerated: logs.filter(l => l.event === 'CSRF_TOKEN_GENERATED').length, totalValidations: logs.filter(l => l.event === 'CSRF_TOKEN_VALIDATED').length, failedValidations: logs.filter(l => l.event === 'CSRF_TOKEN_VALIDATED' && !l.isValid).length, uniqueUsers: new Set(logs.map(l => l.userId)).size }, failedAttempts: this.analyzeFailedAttempts(logs), suspiciousActivity: this.detectSuspiciousActivity(logs), complianceStatus: this.checkCompliance(logs) }; } analyzeFailedAttempts(logs) { const failedLogs = logs.filter(l => l.event === 'CSRF_TOKEN_VALIDATED' && !l.isValid); return { byUser: this.groupBy(failedLogs, 'userId'), byIP: this.groupBy(failedLogs, l => l.metadata.ip), byTime: this.groupByTime(failedLogs) }; } detectSuspiciousActivity(logs) { const suspicious = []; // 检测异常的验证失败率 const userFailureRates = this.calculateUserFailureRates(logs); Object.entries(userFailureRates).forEach(([userId, rate]) => { if (rate > 0.5) { // 失败率超过 50% suspicious.push({ type: 'HIGH_FAILURE_RATE', userId, rate }); } }); return suspicious; } checkCompliance(logs) { // 检查是否符合安全标准 const checks = { hasAuditLogs: logs.length > 0, hasTokenExpiry: true, // 需要从配置检查 hasSecureTransmission: true, // 需要从配置检查 hasProperValidation: true }; return { compliant: Object.values(checks).every(v => v), checks }; }}监控和告警1. 实时监控// CSRF 攻击实时监控class CSRFMonitor { constructor(metricsService, alertService) { this.metrics = metricsService; this.alerts = alertService; this.thresholds = { failedValidationsPerMinute: 10, suspiciousIPRate: 5, unusualUserAgentRate: 3 }; } async monitorValidation(userId, token, isValid, metadata) { // 记录指标 this.metrics.increment('csrf.validations.total'); if (!isValid) { this.metrics.increment('csrf.validations.failed'); await this.checkFailureThresholds(userId, metadata); } else { this.metrics.increment('csrf.validations.success'); } } async checkFailureThresholds(userId, metadata) { const recentFailures = await this.getRecentFailures(60); // 最近 60 秒 if (recentFailures.length > this.thresholds.failedValidationsPerMinute) { await this.alerts.send({ severity: 'HIGH', message: 'High CSRF validation failure rate detected', details: { count: recentFailures.length, timeWindow: '60s' } }); } // 检查特定 IP 的失败率 const ipFailures = recentFailures.filter(f => f.metadata.ip === metadata.ip); if (ipFailures.length > this.thresholds.suspiciousIPRate) { await this.alerts.send({ severity: 'MEDIUM', message: 'Suspicious IP detected', details: { ip: metadata.ip, failures: ipFailures.length } }); } }}2. 仪表板// CSRF 安全仪表板class CSRFSecurityDashboard { constructor(monitor, reportGenerator) { this.monitor = monitor; this.reportGenerator = reportGenerator; } async getDashboardData(timeRange = '24h') { const report = await this.reportGenerator.generateReport( this.getStartDate(timeRange), new Date() ); const metrics = await this.monitor.getMetrics(timeRange); return { overview: { totalValidations: metrics.validations.total, successRate: this.calculateSuccessRate(metrics), activeUsers: report.summary.uniqueUsers }, security: { failedAttempts: report.summary.failedValidations, suspiciousActivity: report.suspiciousActivity.length, complianceStatus: report.complianceStatus.compliant }, trends: this.calculateTrends(metrics), alerts: this.getRecentAlerts() }; }}企业级 CSRF 防护需要综合考虑架构、性能、可用性和合规性,构建一个全面、可靠的安全防护体系。
阅读 0·2月21日 16:10

CSRF 防护的性能影响有哪些,如何进行优化?

CSRF 防护的性能影响和优化是生产环境中需要重点考虑的问题,特别是在高流量和高并发的场景下。CSRF 防护的性能影响1. Token 生成开销// Token 生成的性能测试const crypto = require('crypto');function benchmarkTokenGeneration(iterations = 10000) { const start = Date.now(); for (let i = 0; i < iterations; i++) { crypto.randomBytes(32).toString('hex'); } const duration = Date.now() - start; const avgTime = duration / iterations; return { totalDuration: duration, iterations, avgTimePerToken: avgTime, tokensPerSecond: 1000 / avgTime };}// 测试结果示例const result = benchmarkTokenGeneration();console.log(result);// {// totalDuration: 234,// iterations: 10000,// avgTimePerToken: 0.0234,// tokensPerSecond: 42735// }2. Token 验证开销// Token 验证的性能测试async function benchmarkTokenValidation(iterations = 10000) { const tokens = []; // 预生成 Token for (let i = 0; i < iterations; i++) { tokens.push(crypto.randomBytes(32).toString('hex')); } const start = Date.now(); // 模拟验证过程 for (const token of tokens) { // 假设的验证逻辑 const isValid = token.length === 64 && /^[a-f0-9]+$/.test(token); } const duration = Date.now() - start; const avgTime = duration / iterations; return { totalDuration: duration, iterations, avgTimePerValidation: avgTime, validationsPerSecond: 1000 / avgTime };}3. 数据库查询开销// 数据库查询性能测试async function benchmarkDatabaseQueries(iterations = 1000) { const queries = []; for (let i = 0; i < iterations; i++) { const userId = `user_${i}`; const token = crypto.randomBytes(32).toString('hex'); queries.push( db.query('SELECT * FROM csrf_tokens WHERE user_id = ? AND token = ?', [userId, token]) ); } const start = Date.now(); await Promise.all(queries); const duration = Date.now() - start; return { totalDuration: duration, iterations, avgTimePerQuery: duration / iterations, queriesPerSecond: 1000 / (duration / iterations) };}性能优化策略1. Token 缓存优化// 使用 Redis 缓存 Tokenconst redis = require('redis');const client = redis.createClient();class CachedTokenService { constructor() { this.localCache = new Map(); this.cacheTTL = 300000; // 5 分钟 this.maxCacheSize = 10000; } async getToken(userId) { // 首先检查本地缓存 const cached = this.localCache.get(userId); if (cached && Date.now() - cached.timestamp < this.cacheTTL) { return cached.token; } // 从 Redis 获取 const redisToken = await client.get(`csrf:${userId}`); if (redisToken) { this.updateLocalCache(userId, redisToken); return redisToken; } // 生成新 Token const newToken = crypto.randomBytes(32).toString('hex'); await client.setex(`csrf:${userId}`, 3600, newToken); this.updateLocalCache(userId, newToken); return newToken; } updateLocalCache(userId, token) { // LRU 缓存策略 if (this.localCache.size >= this.maxCacheSize) { const oldestKey = this.localCache.keys().next().value; this.localCache.delete(oldestKey); } this.localCache.set(userId, { token, timestamp: Date.now() }); }}2. 批量 Token 验证// 批量验证 Token 以减少数据库查询class BatchTokenValidator { constructor(db) { this.db = db; this.batchSize = 100; } async validateBatch(requests) { const results = []; for (let i = 0; i < requests.length; i += this.batchSize) { const batch = requests.slice(i, i + this.batchSize); const batchResults = await this.validateSingleBatch(batch); results.push(...batchResults); } return results; } async validateSingleBatch(requests) { // 构造批量查询 const userIds = [...new Set(requests.map(r => r.userId))]; const tokens = requests.map(r => r.token); // 单次数据库查询 const validTokens = await this.db.query( 'SELECT user_id, token FROM csrf_tokens WHERE user_id IN (?) AND token IN (?)', [userIds, tokens] ); // 构造查找表 const validTokenSet = new Set( validTokens.map(t => `${t.user_id}:${t.token}`) ); // 验证每个请求 return requests.map(req => ({ userId: req.userId, token: req.token, isValid: validTokenSet.has(`${req.userId}:${req.token}`) })); }}3. 异步 Token 刷新// 异步刷新 Token 以避免阻塞请求class AsyncTokenRefresher { constructor(tokenService) { this.tokenService = tokenService; this.refreshQueue = new Map(); this.refreshInProgress = new Set(); } async getToken(userId) { // 检查是否有正在进行的刷新 if (this.refreshInProgress.has(userId)) { return await this.waitForRefresh(userId); } // 获取当前 Token const currentToken = await this.tokenService.getToken(userId); // 检查是否需要刷新 if (this.shouldRefreshToken(currentToken)) { this.scheduleRefresh(userId); } return currentToken; } shouldRefreshToken(token) { // 检查 Token 是否即将过期(例如剩余时间 < 10 分钟) const tokenData = this.parseToken(token); const timeToExpiry = tokenData.expiresAt - Date.now(); return timeToExpiry < 600000; } scheduleRefresh(userId) { if (this.refreshInProgress.has(userId)) { return; } this.refreshInProgress.add(userId); // 异步刷新 setImmediate(async () => { try { const newToken = await this.tokenService.generateToken(userId); this.resolveRefreshQueue(userId, newToken); } catch (error) { this.rejectRefreshQueue(userId, error); } finally { this.refreshInProgress.delete(userId); } }); } waitForRefresh(userId) { return new Promise((resolve, reject) => { if (!this.refreshQueue.has(userId)) { this.refreshQueue.set(userId, []); } this.refreshQueue.get(userId).push({ resolve, reject }); }); } resolveRefreshQueue(userId, token) { const queue = this.refreshQueue.get(userId) || []; queue.forEach(item => item.resolve(token)); this.refreshQueue.delete(userId); } rejectRefreshQueue(userId, error) { const queue = this.refreshQueue.get(userId) || []; queue.forEach(item => item.reject(error)); this.refreshQueue.delete(userId); }}负载测试和监控1. 负载测试// 使用 Artillery 进行负载测试// load-test.ymlconfig: target: "http://localhost:3000" phases: - duration: 60 arrivalRate: 100 name: "Warm up" - duration: 120 arrivalRate: 500 name: "Ramp up" - duration: 300 arrivalRate: 1000 name: "Sustained load"scenarios: - name: "CSRF Token Generation" flow: - get: url: "/api/csrf-token" - name: "CSRF Token Validation" flow: - post: url: "/api/submit" headers: X-CSRF-Token: "{{ $randomString() }}"# 运行负载测试artillery run load-test.yml2. 性能监控// 性能监控中间件const prometheus = require('prom-client');// 创建指标const csrfTokenGenerationDuration = new prometheus.Histogram({ name: 'csrf_token_generation_duration_seconds', help: 'Duration of CSRF token generation', labelNames: ['status']});const csrfTokenValidationDuration = new prometheus.Histogram({ name: 'csrf_token_validation_duration_seconds', help: 'Duration of CSRF token validation', labelNames: ['status']});const csrfCacheHitRate = new prometheus.Gauge({ name: 'csrf_cache_hit_rate', help: 'CSRF token cache hit rate'});// 监控中间件function csrfMetricsMiddleware(req, res, next) { const start = Date.now(); res.on('finish', () => { const duration = (Date.now() - start) / 1000; if (req.path === '/api/csrf-token') { csrfTokenGenerationDuration .labels({ status: res.statusCode }) .observe(duration); } else if (req.method !== 'GET') { csrfTokenValidationDuration .labels({ status: res.statusCode }) .observe(duration); } }); next();}// 暴露指标端点app.get('/metrics', (req, res) => { res.set('Content-Type', prometheus.register.contentType); res.end(prometheus.register.metrics());});3. 性能分析// 性能分析工具class CSRFPerformanceAnalyzer { constructor() { this.metrics = { tokenGeneration: [], tokenValidation: [], cacheHits: 0, cacheMisses: 0 }; } recordTokenGeneration(duration) { this.metrics.tokenGeneration.push({ duration, timestamp: Date.now() }); } recordTokenValidation(duration, fromCache) { this.metrics.tokenValidation.push({ duration, fromCache, timestamp: Date.now() }); if (fromCache) { this.metrics.cacheHits++; } else { this.metrics.cacheMisses++; } } analyze() { const avgGenerationTime = this.calculateAverage( this.metrics.tokenGeneration.map(m => m.duration) ); const avgValidationTime = this.calculateAverage( this.metrics.tokenValidation.map(m => m.duration) ); const cacheHitRate = this.metrics.cacheHits / (this.metrics.cacheHits + this.metrics.cacheMisses); return { avgTokenGenerationTime: avgGenerationTime, avgTokenValidationTime: avgValidationTime, cacheHitRate, totalTokenGenerations: this.metrics.tokenGeneration.length, totalTokenValidations: this.metrics.tokenValidation.length }; } calculateAverage(values) { if (values.length === 0) return 0; return values.reduce((sum, val) => sum + val, 0) / values.length; }}优化建议1. 选择合适的 Token 存储方案// 不同存储方案的性能对比const storageOptions = { redis: { readLatency: '1-5ms', writeLatency: '1-5ms', scalability: 'High', complexity: 'Medium', bestFor: 'Distributed systems, high traffic' }, database: { readLatency: '10-50ms', writeLatency: '10-50ms', scalability: 'Medium', complexity: 'Low', bestFor: 'Simple applications, low traffic' }, memory: { readLatency: '<1ms', writeLatency: '<1ms', scalability: 'Low', complexity: 'Low', bestFor: 'Single instance, low traffic' }};2. 实施缓存策略// 多级缓存策略class MultiLevelCache { constructor() { this.l1Cache = new Map(); // 内存缓存 this.l2Cache = null; // Redis 缓存 this.l3Cache = null; // 数据库 } async get(key) { // L1: 内存缓存 if (this.l1Cache.has(key)) { return this.l1Cache.get(key); } // L2: Redis 缓存 if (this.l2Cache) { const value = await this.l2Cache.get(key); if (value) { this.l1Cache.set(key, value); return value; } } // L3: 数据库 if (this.l3Cache) { const value = await this.l3Cache.get(key); if (value) { this.l1Cache.set(key, value); if (this.l2Cache) { await this.l2Cache.set(key, value); } return value; } } return null; }}3. 优化 Token 长度和复杂度// Token 长度和复杂度的权衡const tokenConfigurations = { minimal: { length: 16, charset: '0123456789abcdef', entropy: 64, // bits collisionProbability: 'Very low', performance: 'Best' }, balanced: { length: 32, charset: '0123456789abcdef', entropy: 128, // bits collisionProbability: 'Extremely low', performance: 'Good' }, secure: { length: 64, charset: '0123456789abcdef', entropy: 256, // bits collisionProbability: 'Negligible', performance: 'Acceptable' }};// 根据需求选择配置function selectTokenConfig(requirements) { if (requirements.performance === 'critical') { return tokenConfigurations.minimal; } else if (requirements.security === 'critical') { return tokenConfigurations.secure; } else { return tokenConfigurations.balanced; }}CSRF 防护的性能优化需要在安全性和性能之间找到平衡点,通过合理的架构设计和优化策略,可以在保证安全的同时提供良好的性能表现。
阅读 0·2月19日 21:06

REST API 中如何防护 CSRF 攻击,有哪些特殊考虑?

REST API 中的 CSRF 防护与传统 Web 应用有所不同,因为 REST API 通常使用 JSON 格式进行数据交换,并且可能被各种客户端(Web、移动应用、第三方服务)调用。REST API 中 CSRF 的特殊性1. 客户端多样性Web 应用:浏览器环境,自动发送 Cookie移动应用:原生环境,需要手动管理认证第三方服务:API 调用,可能使用不同的认证方式2. 请求格式传统 Web:表单提交,Content-Type: application/x-www-form-urlencodedREST API:JSON 数据,Content-Type: application/json3. 认证方式Cookie 认证:容易受到 CSRF 攻击Token 认证:JWT、OAuth 等,相对安全混合认证:Cookie + TokenREST API CSRF 防护策略1. 使用 Token 认证(推荐)// JWT Token 认证function authenticateJWT(req, res, next) { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { return res.status(401).send('No token provided'); } try { const decoded = jwt.verify(token, JWT_SECRET); req.user = decoded; next(); } catch (error) { return res.status(401).send('Invalid token'); }}// 保护路由app.post('/api/transfer', authenticateJWT, (req, res) => { // 处理转账请求});优势:Token 存储在客户端(localStorage 或内存)不会自动发送,天然防护 CSRF适合移动应用和第三方集成2. 自定义请求头验证// 生成 CSRF Tokenfunction generateCSRFToken() { return crypto.randomBytes(32).toString('hex');}// 设置 Tokenapp.get('/api/csrf-token', (req, res) => { const token = generateCSRFToken(); req.session.csrfToken = token; res.json({ csrfToken: token });});// 验证自定义头function validateCSRFHeader(req, res, next) { const token = req.headers['x-csrf-token']; if (!token || token !== req.session.csrfToken) { return res.status(403).send('Invalid CSRF token'); } next();}// 保护路由app.post('/api/transfer', validateCSRFHeader, (req, res) => { // 处理请求});前端实现:// 获取 Tokenasync function getCSRFToken() { const response = await fetch('/api/csrf-token'); const data = await response.json(); return data.csrfToken;}// 发送请求async function makeRequest() { const token = await getCSRFToken(); await fetch('/api/transfer', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': token }, body: JSON.stringify({ to: 'user123', amount: 100 }) });}3. SameSite Cookie 配置app.use(session({ secret: 'secret', cookie: { httpOnly: true, secure: true, sameSite: 'strict' // 或 'lax' }}));4. Origin 头验证function validateOrigin(req, res, next) { const origin = req.headers.origin; const allowedOrigins = ['https://example.com', 'https://app.example.com']; if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') { return next(); } if (!origin || !allowedOrigins.includes(origin)) { return res.status(403).send('Invalid origin'); } next();}不同场景的防护方案场景 1:纯 Web 应用(浏览器)// 使用 Cookie + CSRF Tokenapp.use(session({ secret: 'secret', cookie: { httpOnly: true, secure: true, sameSite: 'lax' }}));app.use(csrf({ cookie: true }));app.post('/api/transfer', (req, res) => { // req.csrfToken() 可用于前端});场景 2:移动应用 + Web 应用// 混合认证策略function authenticate(req, res, next) { // 优先使用 JWT Token const authHeader = req.headers.authorization; if (authHeader) { return authenticateJWT(req, res, next); } // 回退到 Cookie 认证 if (req.session.userId) { req.user = { id: req.session.userId }; return next(); } return res.status(401).send('Authentication required');}// CSRF 防护仅对 Cookie 认证生效function csrfProtection(req, res, next) { if (req.headers.authorization) { // JWT 认证,跳过 CSRF 验证 return next(); } // Cookie 认证,需要 CSRF 验证 if (req.body._csrf !== req.session.csrfToken) { return res.status(403).send('Invalid CSRF token'); } next();}场景 3:第三方 API 集成// API Key 认证function authenticateAPIKey(req, res, next) { const apiKey = req.headers['x-api-key']; if (!apiKey) { return res.status(401).send('API key required'); } // 验证 API Key const user = await validateAPIKey(apiKey); if (!user) { return res.status(401).send('Invalid API key'); } req.user = user; next();}// API Key 认证不需要 CSRF 防护app.post('/api/transfer', authenticateAPIKey, (req, res) => { // 处理请求});CORS 配置与 CSRFconst corsOptions = { origin: function (origin, callback) { const allowedOrigins = ['https://example.com', 'https://app.example.com']; // 允许没有 origin 的请求(如移动应用) if (!origin) return callback(null, true); if (allowedOrigins.indexOf(origin) !== -1) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, credentials: true, // 允许发送 Cookie methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token']};app.use(cors(corsOptions));最佳实践总结优先使用 Token 认证:JWT、OAuth 等天然防护 CSRFCookie 认证必须防护:SameSite + CSRF Token自定义请求头:比表单字段更安全Origin 头验证:补充防护措施CORS 正确配置:限制允许的来源分层防护:多种措施组合使用REST API 的 CSRF 防护需要根据具体的使用场景和客户端类型来选择合适的策略,没有通用的解决方案。
阅读 0·2月19日 19:55

SameSite Cookie 属性如何防止 CSRF 攻击?

SameSite Cookie 属性是防御 CSRF 攻击的重要机制,它控制 Cookie 在跨站请求中的发送行为。SameSite 属性概述SameSite 是 Cookie 的一个属性,用于指示浏览器是否应该在跨站请求中发送该 Cookie。它有三个可选值:Strict、Lax 和 None。属性值详解1. SameSite=Strict行为:只在同站请求中发送 Cookie跨站请求(包括导航)都不会发送 Cookie适用场景:银行、支付等高安全性应用敏感操作(如转账、修改密码)不需要跨站功能的应用示例:// 设置 SameSite=Strictdocument.cookie = 'sessionid=abc123; SameSite=Strict; Secure; HttpOnly';优点:提供最强的 CSRF 防护完全阻止跨站请求携带 Cookie缺点:用户从外部链接进入时需要重新登录可能影响用户体验2. SameSite=Lax(推荐)行为:允许某些跨站请求发送 Cookie阻止大多数 CSRF 攻击允许的跨站请求:顶级导航(GET 请求)链接跳转(<a> 标签)表单 GET 请求阻止的跨站请求:POST 请求(表单提交)AJAX 请求<iframe>、<img>、<script> 等资源加载示例:// 设置 SameSite=Laxdocument.cookie = 'sessionid=abc123; SameSite=Lax; Secure; HttpOnly';适用场景:大多数 Web 应用需要外部链接跳转的应用平衡安全性和用户体验优点:提供良好的 CSRF 防护用户体验较好现代浏览器默认值缺点:某些跨站 POST 请求可能受影响需要确保应用兼容性3. SameSite=None行为:允许所有跨站请求发送 Cookie必须配合 Secure 属性使用示例:// 设置 SameSite=Nonedocument.cookie = 'sessionid=abc123; SameSite=None; Secure; HttpOnly';适用场景:需要跨站功能的应用第三方登录(如 OAuth)嵌入式内容优点:不影响现有跨站功能兼容旧应用缺点:无法防御 CSRF 攻击需要其他防护措施浏览器支持情况主流浏览器支持Chrome:51+ 版本支持,80+ 版本默认 LaxFirefox:60+ 版本支持Safari:12+ 版本支持Edge:79+ 版本支持Opera:39+ 版本支持兼容性处理// 检测浏览器是否支持 SameSitefunction setCookie(name, value, days) { let expires = ''; if (days) { const date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); expires = '; expires=' + date.toUTCString(); } // 现代浏览器 let sameSite = '; SameSite=Lax'; // 旧版浏览器不支持 SameSite const cookieString = name + '=' + value + expires + sameSite + '; path=/; Secure; HttpOnly'; document.cookie = cookieString;}同站与跨站的定义同站(Same-Site)相同的顶级域名(eTLD+1)例如:https://example.com 和 https://www.example.com 是同站https://app.example.com 和 https://api.example.com 是同站跨站(Cross-Site)不同的顶级域名例如:https://example.com 和 https://evil.com 是跨站https://example.com 和 https://example.net 是跨站实际应用示例1. 银行应用(Strict)// 高安全性要求document.cookie = 'sessionid=abc123; SameSite=Strict; Secure; HttpOnly; Max-Age=3600';2. 电商网站(Lax)// 平衡安全性和用户体验document.cookie = 'sessionid=abc123; SameSite=Lax; Secure; HttpOnly; Max-Age=86400';3. 第三方登录(None)// 需要跨站功能document.cookie = 'oauth_token=xyz789; SameSite=None; Secure; HttpOnly; Max-Age=3600';框架集成Express.jsapp.use(session({ secret: 'your-secret', cookie: { secure: true, httpOnly: true, sameSite: 'lax' }}));Spring Boot@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); }}Django# settings.pySESSION_COOKIE_SECURE = TrueSESSION_COOKIE_HTTPONLY = TrueSESSION_COOKIE_SAMESITE = 'Lax'CSRF_COOKIE_SECURE = TrueCSRF_COOKIE_HTTPONLY = TrueCSRF_COOKIE_SAMESITE = 'Lax'常见问题1. SameSite=None 不生效原因:缺少 Secure 属性解决:必须同时设置 Secure 属性// 错误document.cookie = 'sessionid=abc123; SameSite=None';// 正确document.cookie = 'sessionid=abc123; SameSite=None; Secure';2. 跨站 POST 请求失败原因:SameSite=Lax 阻止了跨站 POST 请求解决:使用 SameSite=None(需要其他 CSRF 防护)改用同站请求使用 CSRF Token3. 第三方登录失败原因:SameSite 属性阻止了跨站 Cookie解决:为特定 Cookie 设置 SameSite=None使用 OAuth 2.0 的授权码模式使用 PostMessage 通信最佳实践1. 默认使用 SameSite=Lax// 大多数应用的最佳选择document.cookie = 'sessionid=abc123; SameSite=Lax; Secure; HttpOnly';2. 敏感操作使用 SameSite=Strict// 高安全性要求document.cookie = 'admin_session=xyz789; SameSite=Strict; Secure; HttpOnly';3. 避免 SameSite=None// 尽量避免,除非必要// 如果必须使用,配合其他防护措施document.cookie = 'third_party_token=abc123; SameSite=None; Secure; HttpOnly';4. 配合其他防护措施CSRF TokenOrigin/Referer 验证自定义请求头5. 测试兼容性在不同浏览器中测试测试跨站场景测试第三方集成总结SameSite Cookie 属性是防御 CSRF 攻击的有效手段。推荐使用 SameSite=Lax 作为默认配置,它在提供良好 CSRF 防护的同时保持良好的用户体验。对于高安全性要求的应用,可以使用 SameSite=Strict。尽量避免使用 SameSite=None,除非确实需要跨站功能,并配合其他防护措施。
阅读 0·2月19日 19:35

SameSite Cookie 属性如何防护 CSRF 攻击,有哪些使用场景?

SameSite Cookie 属性是现代浏览器提供的一种有效防护 CSRF 攻击的机制,它通过控制 Cookie 在跨站请求中的发送行为来增强安全性。SameSite 属性的三种值1. Strict(严格模式)Cookie 只在同站请求中发送跨站请求不会携带 Cookie提供最强的 CSRF 防护可能影响用户体验(如从外部链接点击进入网站时不会携带 Cookie)2. Lax(宽松模式,推荐)允许某些安全的跨站请求携带 Cookie允许的情况:GET 请求顶级导航(如点击链接)预加载请求不允许的情况:POST、PUT、DELETE 等修改性请求iframe、image、script 等资源请求平衡了安全性和用户体验3. None(不限制)允许所有跨站请求携带 Cookie必须配合 Secure 属性使用不提供 CSRF 防护仅在特定场景下使用(如第三方登录)实现方式设置 SameSite Cookie// Node.js Express 示例res.cookie('sessionId', 'abc123', { httpOnly: true, secure: true, sameSite: 'lax' // 或 'strict', 'none'});// PHP 示例setcookie('sessionId', 'abc123', [ 'httponly' => true, 'secure' => true, 'samesite' => 'Lax']);SameSite 属性的兼容性现代浏览器:Chrome 51+、Firefox 60+、Safari 12+、Edge 79+旧版浏览器:不支持 SameSite 属性,需要其他防护措施移动浏览器:iOS Safari 12.2+、Android Chrome 51+最佳实践默认使用 Lax 模式:提供良好的 CSRF 防护保持正常的用户体验适用于大多数应用场景敏感操作使用 Strict 模式:涉及资金交易、权限变更等敏感操作可以在特定路由或页面设置更严格的策略配合其他防护措施:CSRF TokenReferer 头验证自定义 HTTP 头渐进增强策略:检测浏览器是否支持 SameSite不支持时回退到其他防护机制注意事项Secure 属性要求:SameSite=None 必须配合 Secure 属性需要使用 HTTPS 协议子域名行为:SameSite 将子域名视为同站a.example.com 和 b.example.com 是同站关系测试验证:在不同浏览器中测试行为验证跨站请求的正确处理SameSite Cookie 属性是防护 CSRF 攻击的重要工具,但应该作为多层防护策略的一部分,而不是唯一的防护措施。
阅读 0·2月19日 19:19

什么是双重提交 Cookie 防护 CSRF 的原理和实现方式?

双重提交 Cookie(Double Submit Cookie)是一种 CSRF 防护技术,它通过在 Cookie 和请求参数中同时存储相同的 Token 来验证请求的合法性。双重提交 Cookie 的基本原理Token 生成:服务器生成一个随机的 CSRF Token双重存储:Token 同时存储在 Cookie 和请求参数中验证逻辑:服务器验证 Cookie 中的 Token 和请求参数中的 Token 是否匹配实现步骤1. 生成 Tokenfunction generateCSRFToken() { return crypto.randomBytes(32).toString('hex');}// 中间件:生成并设置 Tokenfunction csrfTokenMiddleware(req, res, next) { const token = generateCSRFToken(); res.cookie('csrfToken', token, { httpOnly: false, // JavaScript 需要读取 secure: true, sameSite: 'strict' }); res.locals.csrfToken = token; next();}2. 在表单中包含 Token<form action="/api/submit" method="POST"> <input type="hidden" name="csrfToken" value="<%= csrfToken %>"> <!-- 其他表单字段 --> <button type="submit">提交</button></form><!-- 或者通过 JavaScript 设置 --><script>const form = document.querySelector('form');const csrfToken = document.querySelector('meta[name="csrf-token"]').content;const input = document.createElement('input');input.type = 'hidden';input.name = 'csrfToken';input.value = csrfToken;form.appendChild(input);</script>3. 验证 Tokenfunction validateDoubleSubmitCookie(req) { const cookieToken = req.cookies.csrfToken; const paramToken = req.body.csrfToken || req.query.csrfToken; if (!cookieToken || !paramToken) { return false; } // 使用恒定时间比较防止时序攻击 return crypto.timingSafeEqual( Buffer.from(cookieToken), Buffer.from(paramToken) );}// 验证中间件function csrfProtection(req, res, next) { if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') { return next(); } if (!validateDoubleSubmitCookie(req)) { return res.status(403).send('CSRF token validation failed'); } next();}工作原理为什么双重提交有效?同源策略:恶意网站无法读取目标网站的 Cookie跨站请求限制:恶意网站无法在请求参数中包含正确的 Token匹配验证:只有同源请求才能同时访问 Cookie 和设置请求参数攻击场景分析<!-- 恶意网站尝试发起 CSRF 攻击 --><form action="https://example.com/api/transfer" method="POST"> <input type="hidden" name="to" value="attacker"> <input type="hidden" name="amount" value="1000"> <!-- 无法获取正确的 csrfToken --></form><script> document.querySelector('form').submit();</script>恶意网站可以发起请求浏览器会自动发送 Cookie 中的 Token但恶意网站无法在请求参数中包含正确的 Token服务器验证失败,拒绝请求优势无需服务器状态:不需要在服务器端存储 Token易于实现:实现相对简单可扩展性:适合分布式系统性能好:不需要查询数据库或 Session局限性Cookie 安全性:如果 Cookie 被窃取(XSS),防护失效需要配合 HttpOnly 使用(但 JavaScript 无法读取)子域名风险:如果子域名存在 XSS 漏洞,可能影响主域名需要谨慎设置 Cookie 的域属性Token 泄露:如果 Token 在 URL 中暴露,可能被记录在日志中应该使用 POST 请求传递 Token最佳实践1. 结合其他防护措施app.use(helmet()); // XSS 防护app.use(cookieSession({ secret: 'secret', cookie: { httpOnly: true, secure: true, sameSite: 'strict' }}));app.use(csrfTokenMiddleware);app.use(csrfProtection);2. Token 刷新策略// 每次请求后刷新 Tokenfunction refreshTokenMiddleware(req, res, next) { if (req.method !== 'GET' && req.method !== 'HEAD') { const newToken = generateCSRFToken(); res.cookie('csrfToken', newToken, { httpOnly: false, secure: true, sameSite: 'strict' }); res.locals.csrfToken = newToken; } next();}3. 安全配置// Cookie 配置res.cookie('csrfToken', token, { httpOnly: false, // 允许 JavaScript 读取 secure: true, // 仅 HTTPS sameSite: 'strict', // 最严格的同站策略 maxAge: 3600000, // 1小时过期 domain: '.example.com' // 谨慎设置域});与 CSRF Token 的对比| 特性 | 双重提交 Cookie | 传统 CSRF Token ||------|----------------|----------------|| 服务器状态 | 无需 | 需要 Session || 实现复杂度 | 简单 | 中等 || 分布式支持 | 优秀 | 需要共享 Session || 安全性 | 良好 | 优秀 || 性能 | 优秀 | 良好 |双重提交 Cookie 是一种有效的 CSRF 防护技术,特别适合分布式系统和需要高性能的场景。但应该与其他安全措施配合使用,提供全面的安全保护。
阅读 0·2月19日 19:15

在前端框架(React、Vue、Angular)中如何实现 CSRF 防护?

前端框架(如 React、Vue、Angular)中的 CSRF 防护需要考虑框架特性和最佳实践,以确保在单页应用(SPA)中提供有效的安全保护。React 中的 CSRF 防护1. 使用 CSRF Token// 获取 CSRF Tokenimport { useEffect, useState } from 'react';function CSRFProtectedForm() { const [csrfToken, setCsrfToken] = useState(''); const [formData, setFormData] = useState({ name: '', email: '' }); useEffect(() => { // 从服务器获取 CSRF Token fetch('/api/csrf-token') .then(res => res.json()) .then(data => setCsrfToken(data.csrfToken)); }, []); const handleSubmit = async (e) => { e.preventDefault(); try { const response = await fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken // 在请求头中发送 Token }, body: JSON.stringify(formData) }); if (response.ok) { alert('提交成功!'); } else { alert('提交失败'); } } catch (error) { console.error('Error:', error); } }; return ( <form onSubmit={handleSubmit}> <input type="text" value={formData.name} onChange={(e) => setFormData({...formData, name: e.target.value})} placeholder="姓名" /> <input type="email" value={formData.email} onChange={(e) => setFormData({...formData, email: e.target.value})} placeholder="邮箱" /> <button type="submit">提交</button> </form> );}2. 使用 Axios 拦截器import axios from 'axios';// 创建 Axios 实例const api = axios.create({ baseURL: '/api', withCredentials: true // 允许发送 Cookie});// 请求拦截器:自动添加 CSRF Tokenapi.interceptors.request.use(async (config) => { // 对于需要 CSRF 保护的请求方法 if (['post', 'put', 'patch', 'delete'].includes(config.method)) { try { const response = await axios.get('/api/csrf-token'); config.headers['X-CSRF-Token'] = response.data.csrfToken; } catch (error) { console.error('Failed to get CSRF token:', error); } } return config;});// 使用示例function SubmitForm() { const handleSubmit = async () => { try { await api.post('/submit', { data: 'example' }); alert('提交成功!'); } catch (error) { alert('提交失败'); } }; return <button onClick={handleSubmit}>提交</button>;}Vue 中的 CSRF 防护1. 使用 Vue Router 和 Axios<template> <form @submit.prevent="handleSubmit"> <input v-model="formData.name" placeholder="姓名" /> <input v-model="formData.email" placeholder="邮箱" /> <button type="submit">提交</button> </form></template><script>import axios from 'axios';export default { data() { return { csrfToken: '', formData: { name: '', email: '' } }; }, async created() { // 获取 CSRF Token const response = await axios.get('/api/csrf-token'); this.csrfToken = response.data.csrfToken; }, methods: { async handleSubmit() { try { const response = await axios.post('/api/submit', this.formData, { headers: { 'X-CSRF-Token': this.csrfToken } }); if (response.data.success) { alert('提交成功!'); } } catch (error) { alert('提交失败'); } } }};</script>2. 使用 Vuex 管理 CSRF Token// store/csrf.jsimport axios from 'axios';export default { namespaced: true, state: { token: null }, mutations: { SET_TOKEN(state, token) { state.token = token; } }, actions: { async fetchToken({ commit }) { try { const response = await axios.get('/api/csrf-token'); commit('SET_TOKEN', response.data.csrfToken); } catch (error) { console.error('Failed to fetch CSRF token:', error); } } }};Angular 中的 CSRF 防护1. 使用 HttpClient 拦截器// csrf.interceptor.tsimport { Injectable } from '@angular/core';import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';import { Observable, from } from 'rxjs';import { switchMap } from 'rxjs/operators';@Injectable()export class CsrfInterceptor implements HttpInterceptor { constructor(private http: HttpClient) {} intercept( req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { // 对于需要 CSRF 保护的请求方法 if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) { return from(this.getCsrfToken()).pipe( switchMap(token => { const csrfReq = req.clone({ setHeaders: { 'X-CSRF-Token': token } }); return next.handle(csrfReq); }) ); } return next.handle(req); } private async getCsrfToken(): Promise<string> { const response = await this.http.get<{ csrfToken: string }>('/api/csrf-token').toPromise(); return response.csrfToken; }}// app.module.tsimport { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';import { CsrfInterceptor } from './csrf.interceptor';@NgModule({ imports: [HttpClientModule], providers: [ { provide: HTTP_INTERCEPTORS, useClass: CsrfInterceptor, multi: true } ]})export class AppModule {}通用最佳实践1. Token 刷新策略// 通用 Token 刷新逻辑class CSRFTokenManager { constructor() { this.token = null; this.tokenExpiry = null; } async getToken() { // 检查 Token 是否过期 if (!this.token || Date.now() > this.tokenExpiry) { await this.refreshToken(); } return this.token; } async refreshToken() { const response = await fetch('/api/csrf-token'); const data = await response.json(); this.token = data.csrfToken; this.tokenExpiry = Date.now() + 3600000; // 1 小时后过期 } clearToken() { this.token = null; this.tokenExpiry = null; }}2. 错误处理和重试// 带有重试机制的请求async function makeRequestWithRetry(url, data, maxRetries = 3) { let retries = 0; while (retries < maxRetries) { try { const token = await csrfTokenManager.getToken(); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': token }, body: JSON.stringify(data) }); if (response.status === 403) { // Token 可能已过期,刷新并重试 csrfTokenManager.clearToken(); retries++; continue; } return await response.json(); } catch (error) { retries++; if (retries >= maxRetries) { throw error; } } }}3. 安全配置// Cookie 配置(服务器端)res.cookie('sessionId', sessionId, { httpOnly: true, // 防止 XSS 窃取 secure: true, // 仅 HTTPS sameSite: 'strict', // 最严格的 CSRF 防护 maxAge: 3600000 // 1 小时过期});框架特定的注意事项React使用 Context API 共享 CSRF Token考虑使用 React Query 或 SWR 管理请求在组件卸载时清理 TokenVue使用 Vuex 或 Pinia 管理 Token 状态利用 Vue 的生命周期钩子获取 Token考虑使用 VueUse 的 useFetchAngular使用 HTTP 拦截器自动处理 Token利用依赖注入管理 Token 服务使用 RxJS 处理异步操作前端框架中的 CSRF 防护需要结合框架特性和最佳实践,确保在提供良好用户体验的同时,不牺牲安全性。
阅读 0·2月19日 19:13

移动应用中如何实施 CSRF 防护,有哪些特殊考虑?

移动应用中的 CSRF 防护与 Web 应用有所不同,因为移动应用通常不使用浏览器自动发送 Cookie 的机制,但仍然需要考虑各种安全风险。移动应用 CSRF 的特殊性1. 认证方式差异Web 应用:使用 Cookie 存储 Session ID浏览器自动发送 Cookie容易受到 CSRF 攻击移动应用:使用 Token(JWT、OAuth)手动管理认证信息相对不易受到传统 CSRF 攻击但存在其他安全风险2. 网络环境差异// 移动应用面临的网络挑战const mobileChallenges = { networkUnreliable: '网络不稳定可能导致重放攻击', deviceCompromise: '设备被 Root 或越狱', appTampering: '应用被篡改或重新打包', insecureStorage: '不安全的本地存储'};移动应用 CSRF 防护策略1. 使用 Token 认证(推荐)iOS 实现// Swift - Token 认证管理class TokenManager { static let shared = TokenManager() private let keychain = Keychain() func saveToken(_ token: String) { // 使用 Keychain 安全存储 Token keychain["authToken"] = token } func getToken() -> String? { return keychain["authToken"] } func clearToken() { keychain["authToken"] = nil }}// 网络请求管理器class NetworkManager { static let shared = NetworkManager() private let session = URLSession.shared func request<T: Decodable>( _ endpoint: String, method: String = "GET", body: Data? = nil, completion: @escaping (Result<T, Error>) -> Void ) { guard let url = URL(string: endpoint) else { completion(.failure(NetworkError.invalidURL)) return } var request = URLRequest(url: url) request.httpMethod = method request.httpBody = body // 添加认证 Token if let token = TokenManager.shared.getToken() { request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") } session.dataTask(with: request) { data, response, error in if let error = error { completion(.failure(error)) return } guard let data = data else { completion(.failure(NetworkError.noData)) return } do { let result = try JSONDecoder().decode(T.self, from: data) completion(.success(result)) } catch { completion(.failure(error)) } }.resume() }}Android 实现// Java - Token 认证管理public class TokenManager { private static TokenManager instance; private SharedPreferences preferences; private TokenManager(Context context) { preferences = context.getSharedPreferences("auth", Context.MODE_PRIVATE); } public static synchronized TokenManager getInstance(Context context) { if (instance == null) { instance = new TokenManager(context); } return instance; } public void saveToken(String token) { preferences.edit().putString("authToken", token).apply(); } public String getToken() { return preferences.getString("authToken", null); } public void clearToken() { preferences.edit().remove("authToken").apply(); }}// 网络请求拦截器public class AuthInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); // 添加认证 Token String token = TokenManager.getInstance(context).getToken(); if (token != null) { Request authenticatedRequest = originalRequest.newBuilder() .header("Authorization", "Bearer " + token) .build(); return chain.proceed(authenticatedRequest); } return chain.proceed(originalRequest); }}2. 设备指纹识别// 服务器端 - 设备指纹验证class DeviceFingerprintService { async generateFingerprint(deviceInfo) { const fingerprintData = { deviceId: deviceInfo.deviceId, os: deviceInfo.os, osVersion: deviceInfo.osVersion, appVersion: deviceInfo.appVersion, screenResolution: deviceInfo.screenResolution, timestamp: Date.now() }; // 生成设备指纹 const fingerprint = crypto.createHash('sha256') .update(JSON.stringify(fingerprintData)) .digest('hex'); return fingerprint; } async validateFingerprint(userId, fingerprint) { const storedFingerprint = await this.getStoredFingerprint(userId); if (!storedFingerprint) { // 首次使用,存储指纹 await this.storeFingerprint(userId, fingerprint); return true; } // 验证指纹是否匹配 return storedFingerprint === fingerprint; }}3. 请求签名// Swift - 请求签名class RequestSigner { static func signRequest(_ request: URLRequest, secretKey: String) -> URLRequest { var signedRequest = request // 生成时间戳和随机数 let timestamp = String(Int(Date().timeIntervalSince1970)) let nonce = UUID().uuidString // 构造签名字符串 let method = request.httpMethod ?? "GET" let url = request.url?.absoluteString ?? "" let body = request.httpBody?.base64EncodedString() ?? "" let signString = "\(method)\n\(url)\n\(timestamp)\n\(nonce)\n\(body)" // 生成 HMAC-SHA256 签名 let signature = signString.hmacSHA256(key: secretKey) // 添加签名头 signedRequest.setValue(timestamp, forHTTPHeaderField: "X-Timestamp") signedRequest.setValue(nonce, forHTTPHeaderField: "X-Nonce") signedRequest.setValue(signature, forHTTPHeaderField: "X-Signature") return signedRequest }}// HMAC-SHA256 扩展extension String { func hmacSHA256(key: String) -> String { let keyData = key.data(using: .utf8)! let messageData = self.data(using: .utf8)! var digestData = Data(count: Int(CC_SHA256_DIGEST_LENGTH)) keyData.withUnsafeBytes { keyBytes in messageData.withUnsafeBytes { messageBytes in CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), keyBytes.baseAddress, keyData.count, messageBytes.baseAddress, messageData.count, &digestData) } } return digestData.base64EncodedString() }}// Java - 请求签名public class RequestSigner { public static Request signRequest(Request request, String secretKey) { // 生成时间戳和随机数 String timestamp = String.valueOf(System.currentTimeMillis() / 1000); String nonce = UUID.randomUUID().toString(); // 构造签名字符串 String method = request.method(); String url = request.url().toString(); String body = bodyToString(request.body()); String signString = method + "\n" + url + "\n" + timestamp + "\n" + nonce + "\n" + body; // 生成 HMAC-SHA256 签名 String signature = hmacSHA256(signString, secretKey); // 添加签名头 return request.newBuilder() .addHeader("X-Timestamp", timestamp) .addHeader("X-Nonce", nonce) .addHeader("X-Signature", signature) .build(); } private static String hmacSHA256(String data, String key) { try { Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "HmacSHA256"); mac.init(secretKeySpec); byte[] hash = mac.doFinal(data.getBytes()); return Base64.encodeToString(hash, Base64.NO_WRAP); } catch (Exception e) { throw new RuntimeException("Failed to generate signature", e); } }}4. 双因素认证// Swift - 双因素认证class TwoFactorAuthManager { static func verifyOTP(userId: String, otp: String, completion: @escaping (Bool) -> Void) { // 发送 OTP 到服务器验证 let endpoint = "https://api.example.com/verify-otp" var request = URLRequest(url: URL(string: endpoint)!) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let body = [ "userId": userId, "otp": otp ] request.httpBody = try? JSONSerialization.data(withJSONObject: body) URLSession.shared.dataTask(with: request) { data, response, error in if let httpResponse = response as? HTTPURLResponse { completion(httpResponse.statusCode == 200) } else { completion(false) } }.resume() }}服务器端验证1. Token 验证中间件// Express.js - Token 验证中间件const jwt = require('jsonwebtoken');function authenticateMobile(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'No token provided' }); } const token = authHeader.substring(7); try { const decoded = jwt.verify(token, JWT_SECRET); req.user = decoded; next(); } catch (error) { return res.status(401).json({ error: 'Invalid token' }); }}// 请求签名验证中间件function verifySignature(req, res, next) { const timestamp = req.headers['x-timestamp']; const nonce = req.headers['x-nonce']; const signature = req.headers['x-signature']; if (!timestamp || !nonce || !signature) { return res.status(401).json({ error: 'Missing signature headers' }); } // 验证时间戳(防止重放攻击) const now = Math.floor(Date.now() / 1000); if (Math.abs(now - parseInt(timestamp)) > 300) { // 5 分钟 return res.status(401).json({ error: 'Request expired' }); } // 验证 nonce(防止重放攻击) if (isNonceUsed(nonce)) { return res.status(401).json({ error: 'Nonce already used' }); } // 验证签名 const expectedSignature = generateSignature(req, timestamp, nonce); if (signature !== expectedSignature) { return res.status(401).json({ error: 'Invalid signature' }); } // 标记 nonce 为已使用 markNonceAsUsed(nonce); next();}2. 设备指纹验证// 设备指纹验证中间件function verifyDeviceFingerprint(req, res, next) { const userId = req.user.id; const fingerprint = req.headers['x-device-fingerprint']; if (!fingerprint) { return res.status(401).json({ error: 'Device fingerprint required' }); } deviceFingerprintService.validateFingerprint(userId, fingerprint) .then(isValid => { if (!isValid) { return res.status(401).json({ error: 'Invalid device fingerprint' }); } next(); }) .catch(error => { res.status(500).json({ error: 'Fingerprint validation failed' }); });}最佳实践1. 安全存储// iOS - 使用 Keychainimport Securityclass KeychainHelper { static func save(key: String, data: Data) -> Bool { let query = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, kSecValueData as String: data ] as [String: Any] SecItemDelete(query as CFDictionary) return SecItemAdd(query as CFDictionary, nil) == errSecSuccess } static func load(key: String) -> Data? { let query = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne ] as [String: Any] var dataTypeRef: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) if status == errSecSuccess { return dataTypeRef as? Data } return nil }}// Android - 使用 EncryptedSharedPreferencespublic class SecureStorage { private static EncryptedSharedPreferences preferences; public static void init(Context context) { try { MasterKey masterKey = new MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build(); preferences = (EncryptedSharedPreferences) EncryptedSharedPreferences.create( context, "secure_prefs", masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ); } catch (Exception e) { throw new RuntimeException("Failed to initialize secure storage", e); } } public static void saveString(String key, String value) { preferences.edit().putString(key, value).apply(); } public static String getString(String key) { return preferences.getString(key, null); }}2. 证书绑定// iOS - SSL 证书绑定class CertificatePinning { static func validateCertificate(for serverTrust: SecTrust) -> Bool { // 获取服务器证书 guard let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { return false } // 获取证书数据 let serverCertificateData = SecCertificateCopyData(serverCertificate) as Data // 加载本地证书 guard let localCertificatePath = Bundle.main.path(forResource: "certificate", ofType: "cer"), let localCertificateData = try? Data(contentsOf: URL(fileURLWithPath: localCertificatePath)) else { return false } // 比较证书 return serverCertificateData == localCertificateData }}移动应用的 CSRF 防护需要结合平台特性和安全最佳实践,确保在各种网络环境和设备状态下都能提供有效的安全保护。
阅读 0·2月19日 17:57

在 Spring Boot 中如何实现 CSRF 防护?

在 Spring Boot 中实现 CSRF 防护有多种方式,Spring Security 提供了内置的 CSRF 保护机制。Spring Security CSRF 保护概述Spring Security 默认启用 CSRF 保护,它通过以下方式工作:生成 CSRF Token将 Token 存储在服务器会话中在表单中自动添加 Token验证请求中的 Token配置方式1. 使用默认配置(推荐)Spring Security 默认启用 CSRF 保护,无需额外配置。@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll(); }}2. 自定义 CSRF Token 存储默认使用 HttpSessionCsrfTokenRepository,可以自定义存储方式。使用 Cookie 存储@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() .authorizeRequests() .anyRequest().authenticated(); }}自定义 Token Repositorypublic class CustomCsrfTokenRepository implements CsrfTokenRepository { @Override public CsrfToken generateToken(HttpServletRequest request) { String token = UUID.randomUUID().toString(); return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token); } @Override public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { // 自定义存储逻辑 request.getSession().setAttribute("_csrf", token); } @Override public CsrfToken loadToken(HttpServletRequest request) { // 自定义加载逻辑 return (CsrfToken) request.getSession().getAttribute("_csrf"); }}@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(new CustomCsrfTokenRepository()) .and() .authorizeRequests() .anyRequest().authenticated(); }}3. 禁用 CSRF 保护(不推荐)某些情况下可能需要禁用 CSRF 保护(如 REST API)。@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .disable() .and() .authorizeRequests() .anyRequest().authenticated(); }}4. 部分禁用 CSRF 保护只为特定路径禁用 CSRF 保护。@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .ignoringAntMatchers("/api/**", "/public/**") .and() .authorizeRequests() .antMatchers("/api/**").permitAll() .anyRequest().authenticated(); }}前端集成1. Thymeleaf 模板Spring Security 自动在 Thymeleaf 模板中添加 CSRF Token。<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <title>Login</title></head><body> <form th:action="@{/login}" method="post"> <!-- CSRF Token 自动添加 --> <input type="text" name="username" placeholder="Username"> <input type="password" name="password" placeholder="Password"> <button type="submit">Login</button> </form></body></html>2. JSP 模板<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %><form action="/login" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <input type="text" name="username" placeholder="Username"> <input type="password" name="password" placeholder="Password"> <button type="submit">Login</button></form>3. AJAX 请求// 获取 CSRF Tokenfunction getCsrfToken() { const metaTag = document.querySelector('meta[name="_csrf"]'); const headerName = document.querySelector('meta[name="_csrf_header"]'); return { token: metaTag ? metaTag.getAttribute('content') : '', headerName: headerName ? headerName.getAttribute('content') : 'X-CSRF-TOKEN' };}// 发送 AJAX 请求function sendAjaxRequest(url, data) { const { token, headerName } = getCsrfToken(); return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', [headerName]: token }, body: JSON.stringify(data) });}// 使用示例sendAjaxRequest('/api/data', { name: 'John' }) .then(response => response.json()) .then(data => console.log(data));4. 在 HTML 中添加 Meta 标签<!DOCTYPE html><html><head> <meta name="_csrf" th:content="${_csrf.token}"/> <meta name="_csrf_header" th:content="${_csrf.headerName}"/> <title>My App</title></head><body> <!-- 页面内容 --></body></html>高级配置1. 自定义 CSRF Token 生成器@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(csrfTokenRepository()) .and() .authorizeRequests() .anyRequest().authenticated(); } @Bean public CsrfTokenRepository csrfTokenRepository() { HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository(); repository.setHeaderName("X-CSRF-TOKEN"); repository.setParameterName("_csrf"); return repository; }}2. 自定义 CSRF Token 验证器public class CustomCsrfTokenValidator implements CsrfTokenValidator { @Override public boolean validateToken(HttpServletRequest request, CsrfToken token) { // 自定义验证逻辑 String requestToken = request.getHeader(token.getHeaderName()); return token.getToken().equals(requestToken); }}@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenValidator(new CustomCsrfTokenValidator()) .and() .authorizeRequests() .anyRequest().authenticated(); }}3. 配置 SameSite Cookie@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); serializer.setCookieName("SESSION"); serializer.setCookiePath("/"); serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); serializer.setSameSite("Lax"); serializer.setUseHttpOnlyCookie(true); serializer.setUseSecureCookie(true); return serializer; }}测试 CSRF 保护1. 单元测试@RunWith(SpringRunner.class)@SpringBootTest@AutoConfigureMockMvcpublic class CsrfProtectionTest { @Autowired private MockMvc mockMvc; @Test public void testCsrfProtection() throws Exception { mockMvc.perform(post("/transfer") .contentType(MediaType.APPLICATION_JSON) .content("{\"amount\":100}")) .andExpect(status().isForbidden()); } @Test public void testWithValidCsrfToken() throws Exception { MvcResult result = mockMvc.perform(get("/csrf")) .andReturn(); String csrfToken = result.getResponse().getContentAsString(); mockMvc.perform(post("/transfer") .contentType(MediaType.APPLICATION_JSON) .header("X-CSRF-TOKEN", csrfToken) .content("{\"amount\":100}")) .andExpect(status().isOk()); }}2. 集成测试@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)@AutoConfigureMockMvcpublic class CsrfIntegrationTest { @Autowired private TestRestTemplate restTemplate; @Test public void testCsrfWithRestTemplate() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); // 没有 CSRF Token HttpEntity<String> request = new HttpEntity<>("{\"amount\":100}", headers); ResponseEntity<String> response = restTemplate.postForEntity("/transfer", request, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); }}常见问题1. AJAX 请求 403 错误原因:缺少 CSRF Token解决:在请求头中添加 CSRF Tokenfetch('/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': getCsrfToken() }, body: JSON.stringify(data)});2. 多标签页 Token 失效原因:会话过期或 Token 不匹配解决:确保所有标签页使用同一个会话3. 文件上传失败原因:文件上传无法使用表单 Token解决:使用请求头或预签名 URL最佳实践使用默认配置:Spring Security 默认配置已经足够安全使用 Cookie 存储 Token:便于前端获取为 AJAX 请求添加 Token:确保所有请求都包含 Token定期更新 Token:降低 Token 泄露风险配合其他防护措施:如 SameSite Cookie、Origin 验证测试 CSRF 保护:确保防护机制正常工作总结Spring Boot 通过 Spring Security 提供了完善的 CSRF 保护机制。默认配置已经足够安全,可以根据需要自定义 Token 存储方式和验证逻辑。前端需要确保所有请求都包含有效的 CSRF Token,特别是 AJAX 请求。配合其他防护措施可以构建更强大的安全防护体系。
阅读 0·2月19日 17:52

如何通过验证 Referer 头来防护 CSRF 攻击,有哪些局限性?

验证 Referer 头是防护 CSRF 攻击的一种传统方法,通过检查 HTTP 请求的 Referer 头来验证请求来源的合法性。Referer 头的基本原理Referer 头是 HTTP 请求头的一部分,它包含了发起请求的页面 URL。服务器可以通过检查 Referer 头来判断请求是否来自受信任的来源。验证 Referer 头的实现基本验证逻辑function validateReferer(req, trustedDomains) { const referer = req.headers.referer; if (!referer) { return false; // 拒绝没有 Referer 的请求 } try { const refererUrl = new URL(referer); const refererDomain = refererUrl.hostname; return trustedDomains.some(domain => refererDomain === domain || refererDomain.endsWith('.' + domain) ); } catch (error) { return false; // 无效的 URL }}// 使用示例app.post('/api/transfer', (req, res) => { const trustedDomains = ['example.com', 'www.example.com']; if (!validateReferer(req, trustedDomains)) { return res.status(403).send('Invalid referer'); } // 处理转账请求});验证策略1. 严格匹配Referer 必须完全匹配受信任的域名提供最强的安全性可能影响某些合法请求2. 域名匹配允许子域名更灵活的验证需要确保所有子域名都是受信任的3. 允许空 Referer某些情况下 Referer 可能为空(如直接输入 URL)需要结合其他验证方式降低安全性Referer 头的局限性1. 隐私设置某些浏览器或隐私插件可能阻止发送 Referer用户可以禁用 Referer 头导致合法请求被拒绝2. 可伪造性Referer 头可以被攻击者伪造不是绝对安全的防护方式需要配合其他防护措施3. HTTPS 降级从 HTTPS 页面发起 HTTP 请求时,Referer 可能被移除混合内容场景下的处理复杂4. 浏览器兼容性不同浏览器对 Referer 的处理可能不同某些移动浏览器的行为不一致最佳实践1. 多层防护function csrfProtection(req, res, next) { // 验证 Referer if (!validateReferer(req, trustedDomains)) { return res.status(403).send('Invalid referer'); } // 验证 CSRF Token if (req.body._csrf !== req.session.csrfToken) { return res.status(403).send('Invalid CSRF token'); } next();}2. 配置 Referer-Policy<meta name="referrer" content="strict-origin-when-cross-origin">3. 白名单管理维护受信任的域名列表定期更新和审查支持动态配置4. 日志记录if (!validateReferer(req, trustedDomains)) { logger.warn('Invalid referer attempt', { referer: req.headers.referer, ip: req.ip, path: req.path }); return res.status(403).send('Invalid referer');}适用场景适合使用 Referer 验证的场景内部管理系统API 接口保护作为辅助防护措施不适合单独使用的场景公共网站对安全性要求极高的系统需要支持多种访问方式的场景现代替代方案随着 SameSite Cookie 和 CSRF Token 的普及,Referer 验证通常作为辅助防护手段使用,而不是主要的防护方式。验证 Referer 头虽然有一定的局限性,但在正确的使用场景下,配合其他防护措施,仍然可以提供有效的 CSRF 防护。
阅读 0·2月19日 17:49