🛡️ Web安全防护实战指南

📖 概述

Web安全是现代应用开发中不可忽视的重要环节。本指南将从OWASP Top 10安全风险出发,深入讲解各种安全威胁的原理、检测方法和防护策略,并提供实用的代码示例和工具。

🎯 OWASP Top 10 安全风险

1. 注入攻击 (Injection)

SQL注入防护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// SecurityService.java - 安全服务类
@Service
public class SecurityService {

@Autowired
private JdbcTemplate jdbcTemplate;

// ❌ 不安全的SQL查询
public User getUserByIdUnsafe(String userId) {
String sql = "SELECT * FROM users WHERE id = " + userId;
return jdbcTemplate.queryForObject(sql, User.class);
}

// ✅ 使用参数化查询防止SQL注入
public User getUserByIdSafe(String userId) {
String sql = "SELECT * FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{userId}, User.class);
}

// ✅ 使用PreparedStatement
public List<User> searchUsersSafe(String keyword) {
String sql = "SELECT * FROM users WHERE username LIKE ? OR email LIKE ?";
String searchPattern = "%" + keyword + "%";

return jdbcTemplate.query(sql,
new Object[]{searchPattern, searchPattern},
(rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
user.setEmail(rs.getString("email"));
return user;
});
}

// 输入验证和清理
public boolean isValidUserId(String userId) {
if (userId == null || userId.trim().isEmpty()) {
return false;
}

9. 使用含有已知漏洞的组件 (Using Components with Known Vulnerabilities)

依赖安全扫描工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// DependencySecurityScanner.java - 依赖安全扫描工具
@Component
public class DependencySecurityScanner {

private final Logger logger = LoggerFactory.getLogger(DependencySecurityScanner.class);
private final RestTemplate restTemplate;

public DependencySecurityScanner() {
this.restTemplate = new RestTemplate();
}

// 扫描Maven依赖
public List<VulnerabilityReport> scanMavenDependencies() {
List<VulnerabilityReport> reports = new ArrayList<>();

try {
// 读取pom.xml文件
File pomFile = new File("pom.xml");
if (!pomFile.exists()) {
logger.warn("pom.xml not found");
return reports;
}

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(pomFile);

NodeList dependencies = doc.getElementsByTagName("dependency");

for (int i = 0; i < dependencies.getLength(); i++) {
Element dependency = (Element) dependencies.item(i);

String groupId = getElementValue(dependency, "groupId");
String artifactId = getElementValue(dependency, "artifactId");
String version = getElementValue(dependency, "version");

if (groupId != null && artifactId != null && version != null) {
VulnerabilityReport report = checkVulnerability(groupId, artifactId, version);
if (report.hasVulnerabilities()) {
reports.add(report);
}
}
}

} catch (Exception e) {
logger.error("Failed to scan Maven dependencies", e);
}

return reports;
}

// 检查单个依赖的漏洞
private VulnerabilityReport checkVulnerability(String groupId, String artifactId, String version) {
VulnerabilityReport report = new VulnerabilityReport(groupId, artifactId, version);

try {
// 使用NIST NVD API检查漏洞
String cpe = String.format("cpe:2.3:a:%s:%s:%s:*:*:*:*:*:*:*",
groupId.replace(".", "_"), artifactId, version);

String apiUrl = "https://services.nvd.nist.gov/rest/json/cves/1.0?cpeMatchString=" +
URLEncoder.encode(cpe, StandardCharsets.UTF_8.toString());

ResponseEntity<String> response = restTemplate.getForEntity(apiUrl, String.class);

if (response.getStatusCode().is2xxSuccessful()) {
parseNvdResponse(response.getBody(), report);
}

} catch (Exception e) {
logger.error("Failed to check vulnerability for {}:{}:{}", groupId, artifactId, version, e);
}

return report;
}

// 解析NVD响应
private void parseNvdResponse(String responseBody, VulnerabilityReport report) {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(responseBody);
JsonNode cveItems = root.get("result").get("CVE_Items");

if (cveItems != null && cveItems.isArray()) {
for (JsonNode cveItem : cveItems) {
JsonNode cve = cveItem.get("cve");
String cveId = cve.get("CVE_data_meta").get("ID").asText();

JsonNode description = cve.get("description").get("description_data").get(0);
String desc = description.get("value").asText();

JsonNode impact = cveItem.get("impact");
double baseScore = 0.0;
String severity = "UNKNOWN";

if (impact != null && impact.has("baseMetricV3")) {
JsonNode cvssV3 = impact.get("baseMetricV3").get("cvssV3");
baseScore = cvssV3.get("baseScore").asDouble();
severity = cvssV3.get("baseSeverity").asText();
} else if (impact != null && impact.has("baseMetricV2")) {
JsonNode cvssV2 = impact.get("baseMetricV2").get("cvssV2");
baseScore = cvssV2.get("baseScore").asDouble();
severity = getSeverityFromScore(baseScore);
}

Vulnerability vulnerability = new Vulnerability(cveId, desc, severity, baseScore);
report.addVulnerability(vulnerability);
}
}

} catch (Exception e) {
logger.error("Failed to parse NVD response", e);
}
}

private String getSeverityFromScore(double score) {
if (score >= 9.0) return "CRITICAL";
if (score >= 7.0) return "HIGH";
if (score >= 4.0) return "MEDIUM";
return "LOW";
}

private String getElementValue(Element parent, String tagName) {
NodeList nodeList = parent.getElementsByTagName(tagName);
if (nodeList.getLength() > 0) {
return nodeList.item(0).getTextContent();
}
return null;
}

// 生成安全报告
public SecurityReport generateSecurityReport() {
SecurityReport report = new SecurityReport();

// 扫描依赖漏洞
List<VulnerabilityReport> vulnerabilities = scanMavenDependencies();
report.setVulnerabilityReports(vulnerabilities);

// 检查过期依赖
List<OutdatedDependency> outdatedDeps = checkOutdatedDependencies();
report.setOutdatedDependencies(outdatedDeps);

// 检查许可证合规性
List<LicenseIssue> licenseIssues = checkLicenseCompliance();
report.setLicenseIssues(licenseIssues);

return report;
}

private List<OutdatedDependency> checkOutdatedDependencies() {
// 实现检查过期依赖的逻辑
return new ArrayList<>();
}

private List<LicenseIssue> checkLicenseCompliance() {
// 实现检查许可证合规性的逻辑
return new ArrayList<>();
}
}

// 漏洞报告类
class VulnerabilityReport {
private String groupId;
private String artifactId;
private String version;
private List<Vulnerability> vulnerabilities;

public VulnerabilityReport(String groupId, String artifactId, String version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
this.vulnerabilities = new ArrayList<>();
}

public void addVulnerability(Vulnerability vulnerability) {
this.vulnerabilities.add(vulnerability);
}

public boolean hasVulnerabilities() {
return !vulnerabilities.isEmpty();
}

// getters and setters
}

// 漏洞类
class Vulnerability {
private String cveId;
private String description;
private String severity;
private double score;

public Vulnerability(String cveId, String description, String severity, double score) {
this.cveId = cveId;
this.description = description;
this.severity = severity;
this.score = score;
}

// getters and setters
}

自动化安全更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// securityUpdater.js - 自动化安全更新工具
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const axios = require('axios');

class SecurityUpdater {

constructor() {
this.packageJsonPath = path.join(process.cwd(), 'package.json');
this.vulnerabilityDb = new Map();
}

// 扫描npm依赖漏洞
async scanNpmVulnerabilities() {
try {
console.log('Scanning npm dependencies for vulnerabilities...');

// 运行npm audit
const auditResult = execSync('npm audit --json', { encoding: 'utf8' });
const auditData = JSON.parse(auditResult);

const vulnerabilities = [];

if (auditData.vulnerabilities) {
for (const [packageName, vulnData] of Object.entries(auditData.vulnerabilities)) {
vulnerabilities.push({
package: packageName,
severity: vulnData.severity,
title: vulnData.title,
url: vulnData.url,
range: vulnData.range,
fixAvailable: vulnData.fixAvailable
});
}
}

return vulnerabilities;

} catch (error) {
console.error('Failed to scan npm vulnerabilities:', error.message);
return [];
}
}

// 检查过期包
async checkOutdatedPackages() {
try {
console.log('Checking for outdated packages...');

const outdatedResult = execSync('npm outdated --json', { encoding: 'utf8' });
const outdatedData = JSON.parse(outdatedResult);

const outdatedPackages = [];

for (const [packageName, packageData] of Object.entries(outdatedData)) {
outdatedPackages.push({
package: packageName,
current: packageData.current,
wanted: packageData.wanted,
latest: packageData.latest,
location: packageData.location
});
}

return outdatedPackages;

} catch (error) {
// npm outdated returns non-zero exit code when packages are outdated
if (error.stdout) {
try {
return JSON.parse(error.stdout);
} catch (parseError) {
console.error('Failed to parse outdated packages:', parseError.message);
}
}
return [];
}
}

// 自动修复漏洞
async autoFixVulnerabilities() {
try {
console.log('Attempting to auto-fix vulnerabilities...');

// 尝试自动修复
execSync('npm audit fix', { stdio: 'inherit' });

// 如果需要强制修复
const auditResult = execSync('npm audit --json', { encoding: 'utf8' });
const auditData = JSON.parse(auditResult);

if (auditData.metadata && auditData.metadata.vulnerabilities.total > 0) {
console.log('Some vulnerabilities require manual intervention or force fix');

// 询问是否进行强制修复
const forceFixConfirm = await this.promptForcefix();
if (forceFixConfirm) {
execSync('npm audit fix --force', { stdio: 'inherit' });
}
}

console.log('Vulnerability fix completed');

} catch (error) {
console.error('Failed to auto-fix vulnerabilities:', error.message);
}
}

// 更新依赖到安全版本
async updateToSecureVersions() {
try {
const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf8'));
const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };

const updates = [];

for (const [packageName, currentVersion] of Object.entries(dependencies)) {
const secureVersion = await this.getSecureVersion(packageName, currentVersion);
if (secureVersion && secureVersion !== currentVersion) {
updates.push({
package: packageName,
from: currentVersion,
to: secureVersion
});
}
}

if (updates.length > 0) {
console.log('Updating packages to secure versions:');
updates.forEach(update => {
console.log(` ${update.package}: ${update.from} -> ${update.to}`);
});

// 执行更新
for (const update of updates) {
execSync(`npm install ${update.package}@${update.to}`, { stdio: 'inherit' });
}
}

} catch (error) {
console.error('Failed to update to secure versions:', error.message);
}
}

// 获取包的安全版本
async getSecureVersion(packageName, currentVersion) {
try {
const response = await axios.get(`https://registry.npmjs.org/${packageName}`);
const packageData = response.data;

// 获取最新版本
const latestVersion = packageData['dist-tags'].latest;

// 检查是否有已知漏洞
const hasVulnerabilities = await this.checkPackageVulnerabilities(packageName, currentVersion);

if (hasVulnerabilities) {
return latestVersion;
}

return null;

} catch (error) {
console.error(`Failed to get secure version for ${packageName}:`, error.message);
return null;
}
}

// 检查包是否有漏洞
async checkPackageVulnerabilities(packageName, version) {
try {
// 使用Snyk API或其他漏洞数据库
const response = await axios.get(`https://snyk.io/vuln/npm:${packageName}@${version}`);
return response.status === 200;
} catch (error) {
// 如果API调用失败,假设没有漏洞
return false;
}
}

// 生成安全报告
async generateSecurityReport() {
const report = {
timestamp: new Date().toISOString(),
vulnerabilities: await this.scanNpmVulnerabilities(),
outdatedPackages: await this.checkOutdatedPackages(),
recommendations: []
};

// 添加建议
if (report.vulnerabilities.length > 0) {
report.recommendations.push('Run "npm audit fix" to automatically fix vulnerabilities');
}

if (report.outdatedPackages.length > 0) {
report.recommendations.push('Update outdated packages to their latest versions');
}

// 保存报告
const reportPath = path.join(process.cwd(), 'security-report.json');
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));

console.log(`Security report saved to ${reportPath}`);

return report;
}

// 设置自动化安全检查
setupAutomatedChecks() {
// 创建GitHub Actions工作流
const workflowContent = `
name: Security Audit

on:
schedule:
- cron: '0 2 * * 1' # 每周一凌晨2点运行
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]

jobs:
security-audit:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run security audit
run: npm audit --audit-level moderate

- name: Check for outdated packages
run: npm outdated
continue-on-error: true

- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: \${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
`;

const workflowDir = path.join(process.cwd(), '.github', 'workflows');
if (!fs.existsSync(workflowDir)) {
fs.mkdirSync(workflowDir, { recursive: true });
}

fs.writeFileSync(path.join(workflowDir, 'security-audit.yml'), workflowContent);
console.log('GitHub Actions security workflow created');
}

// 提示用户确认强制修复
async promptForcefix() {
// 在实际应用中,这里应该使用适当的用户输入库
console.log('Some vulnerabilities require breaking changes. Continue with force fix? (y/N)');
return false; // 默认不进行强制修复
}
}

module.exports = SecurityUpdater;

// 使用示例
if (require.main === module) {
const updater = new SecurityUpdater();

(async () => {
try {
await updater.generateSecurityReport();
await updater.autoFixVulnerabilities();
updater.setupAutomatedChecks();
} catch (error) {
console.error('Security update process failed:', error);
process.exit(1);
}
})();
}

10. 不足的日志记录和监控 (Insufficient Logging & Monitoring)

安全日志记录服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
// SecurityLoggingService.java - 安全日志记录服务
@Service
public class SecurityLoggingService {

private final Logger securityLogger = LoggerFactory.getLogger("SECURITY");
private final Logger auditLogger = LoggerFactory.getLogger("AUDIT");
private final RedisTemplate<String, Object> redisTemplate;

@Autowired
public SecurityLoggingService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}

// 记录认证事件
public void logAuthenticationEvent(String username, String event, String ipAddress,
String userAgent, boolean success) {
SecurityEvent securityEvent = SecurityEvent.builder()
.eventType("AUTHENTICATION")
.username(username)
.event(event)
.ipAddress(ipAddress)
.userAgent(userAgent)
.success(success)
.timestamp(Instant.now())
.build();

logSecurityEvent(securityEvent);

// 如果认证失败,增加失败计数
if (!success) {
incrementFailedAttempts(username, ipAddress);
}
}

// 记录授权事件
public void logAuthorizationEvent(String username, String resource, String action,
String ipAddress, boolean success) {
SecurityEvent securityEvent = SecurityEvent.builder()
.eventType("AUTHORIZATION")
.username(username)
.resource(resource)
.action(action)
.ipAddress(ipAddress)
.success(success)
.timestamp(Instant.now())
.build();

logSecurityEvent(securityEvent);
}

// 记录数据访问事件
public void logDataAccessEvent(String username, String dataType, String operation,
String recordId, String ipAddress) {
AuditEvent auditEvent = AuditEvent.builder()
.eventType("DATA_ACCESS")
.username(username)
.dataType(dataType)
.operation(operation)
.recordId(recordId)
.ipAddress(ipAddress)
.timestamp(Instant.now())
.build();

logAuditEvent(auditEvent);
}

// 记录安全威胁事件
public void logSecurityThreat(String threatType, String description, String ipAddress,
String userAgent, String severity) {
SecurityEvent securityEvent = SecurityEvent.builder()
.eventType("SECURITY_THREAT")
.threatType(threatType)
.description(description)
.ipAddress(ipAddress)
.userAgent(userAgent)
.severity(severity)
.timestamp(Instant.now())
.build();

logSecurityEvent(securityEvent);

// 高危威胁立即告警
if ("HIGH".equals(severity) || "CRITICAL".equals(severity)) {
sendSecurityAlert(securityEvent);
}
}

// 记录系统配置变更
public void logConfigurationChange(String username, String configType, String oldValue,
String newValue, String ipAddress) {
AuditEvent auditEvent = AuditEvent.builder()
.eventType("CONFIGURATION_CHANGE")
.username(username)
.configType(configType)
.oldValue(oldValue)
.newValue(newValue)
.ipAddress(ipAddress)
.timestamp(Instant.now())
.build();

logAuditEvent(auditEvent);
}

// 记录文件操作事件
public void logFileOperation(String username, String operation, String filePath,
String ipAddress, boolean success) {
AuditEvent auditEvent = AuditEvent.builder()
.eventType("FILE_OPERATION")
.username(username)
.operation(operation)
.filePath(filePath)
.ipAddress(ipAddress)
.success(success)
.timestamp(Instant.now())
.build();

logAuditEvent(auditEvent);
}

// 记录API调用
public void logApiCall(String username, String endpoint, String method, String ipAddress,
int statusCode, long responseTime) {
AuditEvent auditEvent = AuditEvent.builder()
.eventType("API_CALL")
.username(username)
.endpoint(endpoint)
.method(method)
.ipAddress(ipAddress)
.statusCode(statusCode)
.responseTime(responseTime)
.timestamp(Instant.now())
.build();

logAuditEvent(auditEvent);

// 记录异常状态码
if (statusCode >= 400) {
logSecurityEvent(SecurityEvent.builder()
.eventType("API_ERROR")
.username(username)
.endpoint(endpoint)
.statusCode(statusCode)
.ipAddress(ipAddress)
.timestamp(Instant.now())
.build());
}
}

// 记录安全事件
private void logSecurityEvent(SecurityEvent event) {
try {
String logMessage = formatSecurityEvent(event);
securityLogger.info(logMessage);

// 存储到Redis用于实时监控
String key = "security:events:" + event.getEventType();
redisTemplate.opsForList().leftPush(key, event);
redisTemplate.expire(key, Duration.ofDays(7));

// 存储到数据库(如果需要长期保存)
// saveSecurityEventToDatabase(event);

} catch (Exception e) {
securityLogger.error("Failed to log security event", e);
}
}

// 记录审计事件
private void logAuditEvent(AuditEvent event) {
try {
String logMessage = formatAuditEvent(event);
auditLogger.info(logMessage);

// 存储到Redis
String key = "audit:events:" + event.getEventType();
redisTemplate.opsForList().leftPush(key, event);
redisTemplate.expire(key, Duration.ofDays(30));

} catch (Exception e) {
auditLogger.error("Failed to log audit event", e);
}
}

// 格式化安全事件日志
private String formatSecurityEvent(SecurityEvent event) {
return String.format(
"SECURITY_EVENT | Type: %s | User: %s | IP: %s | Success: %s | Time: %s | Details: %s",
event.getEventType(),
event.getUsername(),
event.getIpAddress(),
event.isSuccess(),
event.getTimestamp(),
event.getDetails()
);
}

// 格式化审计事件日志
private String formatAuditEvent(AuditEvent event) {
return String.format(
"AUDIT_EVENT | Type: %s | User: %s | IP: %s | Time: %s | Details: %s",
event.getEventType(),
event.getUsername(),
event.getIpAddress(),
event.getTimestamp(),
event.getDetails()
);
}

// 增加失败尝试计数
private void incrementFailedAttempts(String username, String ipAddress) {
String userKey = "failed_attempts:user:" + username;
String ipKey = "failed_attempts:ip:" + ipAddress;

redisTemplate.opsForValue().increment(userKey);
redisTemplate.opsForValue().increment(ipKey);

redisTemplate.expire(userKey, Duration.ofMinutes(15));
redisTemplate.expire(ipKey, Duration.ofMinutes(15));

// 检查是否需要锁定
checkAndLockAccount(username, ipAddress);
}

// 检查并锁定账户
private void checkAndLockAccount(String username, String ipAddress) {
Integer userAttempts = (Integer) redisTemplate.opsForValue().get("failed_attempts:user:" + username);
Integer ipAttempts = (Integer) redisTemplate.opsForValue().get("failed_attempts:ip:" + ipAddress);

if (userAttempts != null && userAttempts >= 5) {
lockAccount(username, "Too many failed login attempts");
}

if (ipAttempts != null && ipAttempts >= 10) {
blockIpAddress(ipAddress, "Too many failed login attempts from IP");
}
}

// 锁定账户
private void lockAccount(String username, String reason) {
redisTemplate.opsForValue().set("locked:user:" + username, reason, Duration.ofMinutes(30));

logSecurityEvent(SecurityEvent.builder()
.eventType("ACCOUNT_LOCKED")
.username(username)
.description(reason)
.severity("HIGH")
.timestamp(Instant.now())
.build());
}

// 阻止IP地址
private void blockIpAddress(String ipAddress, String reason) {
redisTemplate.opsForValue().set("blocked:ip:" + ipAddress, reason, Duration.ofHours(1));

logSecurityEvent(SecurityEvent.builder()
.eventType("IP_BLOCKED")
.ipAddress(ipAddress)
.description(reason)
.severity("HIGH")
.timestamp(Instant.now())
.build());
}

// 发送安全告警
private void sendSecurityAlert(SecurityEvent event) {
// 实现告警逻辑(邮件、短信、Slack等)
securityLogger.warn("SECURITY ALERT: {}", formatSecurityEvent(event));
}

// 获取安全统计信息
public SecurityStatistics getSecurityStatistics(LocalDateTime startTime, LocalDateTime endTime) {
// 实现统计逻辑
return SecurityStatistics.builder()
.totalEvents(0L)
.failedLogins(0L)
.blockedIps(0L)
.securityThreats(0L)
.build();
}
}

// 安全事件实体
@Data
@Builder
class SecurityEvent {
private String eventType;
private String username;
private String ipAddress;
private String userAgent;
private String event;
private String resource;
private String action;
private String threatType;
private String description;
private String severity;
private String endpoint;
private Integer statusCode;
private boolean success;
private Instant timestamp;

public String getDetails() {
Map<String, Object> details = new HashMap<>();
if (event != null) details.put("event", event);
if (resource != null) details.put("resource", resource);
if (action != null) details.put("action", action);
if (threatType != null) details.put("threatType", threatType);
if (description != null) details.put("description", description);
if (severity != null) details.put("severity", severity);
if (endpoint != null) details.put("endpoint", endpoint);
if (statusCode != null) details.put("statusCode", statusCode);
if (userAgent != null) details.put("userAgent", userAgent);

return details.toString();
}
}

// 审计事件实体
@Data
@Builder
class AuditEvent {
private String eventType;
private String username;
private String ipAddress;
private String dataType;
private String operation;
private String recordId;
private String configType;
private String oldValue;
private String newValue;
private String filePath;
private String endpoint;
private String method;
private Integer statusCode;
private Long responseTime;
private boolean success;
private Instant timestamp;

public String getDetails() {
Map<String, Object> details = new HashMap<>();
if (dataType != null) details.put("dataType", dataType);
if (operation != null) details.put("operation", operation);
if (recordId != null) details.put("recordId", recordId);
if (configType != null) details.put("configType", configType);
if (oldValue != null) details.put("oldValue", oldValue);
if (newValue != null) details.put("newValue", newValue);
if (filePath != null) details.put("filePath", filePath);
if (endpoint != null) details.put("endpoint", endpoint);
if (method != null) details.put("method", method);
if (statusCode != null) details.put("statusCode", statusCode);
if (responseTime != null) details.put("responseTime", responseTime);

return details.toString();
}
}

// 安全统计信息
@Data
@Builder
class SecurityStatistics {
private Long totalEvents;
private Long failedLogins;
private Long blockedIps;
private Long securityThreats;
}

总结与最佳实践

安全开发生命周期 (SDLC)

  1. 需求分析阶段

    • 识别安全需求
    • 进行威胁建模
    • 定义安全标准
  2. 设计阶段

    • 安全架构设计
    • 数据流分析
    • 访问控制设计
  3. 开发阶段

    • 安全编码规范
    • 代码审查
    • 静态代码分析
  4. 测试阶段

    • 安全测试
    • 渗透测试
    • 漏洞扫描
  5. 部署阶段

    • 安全配置
    • 环境加固
    • 监控部署
  6. 维护阶段

    • 持续监控
    • 安全更新
    • 事件响应

关键安全原则

  1. 纵深防御 - 多层安全控制
  2. 最小权限 - 只授予必要权限
  3. 失败安全 - 默认拒绝访问
  4. 安全设计 - 从设计阶段考虑安全
  5. 持续监控 - 实时安全监控
  6. 定期更新 - 及时修复漏洞

安全检查清单

  • 输入验证和输出编码
  • 身份认证和会话管理
  • 访问控制和权限管理
  • 数据加密和传输安全
  • 错误处理和日志记录
  • 安全配置和环境加固
  • 依赖管理和漏洞扫描
  • 安全测试和代码审查
  • 监控告警和事件响应
  • 安全培训和意识提升

通过实施这些安全防护措施,可以显著提高Web应用的安全性,保护用户数据和系统资源免受各种网络威胁。安全是一个持续的过程,需要不断学习新的威胁和防护技术,保持警惕并及时更新安全措施。
// 只允许数字
return userId.matches(“^\d+$”);
}

// SQL注入检测
public boolean containsSqlInjection(String input) {
    if (input == null) return false;
    
    String[] sqlKeywords = {
        "SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "CREATE",
        "ALTER", "EXEC", "EXECUTE", "UNION", "SCRIPT", "JAVASCRIPT"
    };
    
    String upperInput = input.toUpperCase();
    for (String keyword : sqlKeywords) {
        if (upperInput.contains(keyword)) {
            return true;
        }
    }
    
    // 检测常见SQL注入模式
    String[] injectionPatterns = {
        ".*'.*OR.*'.*=.*'.*",
        ".*'.*UNION.*SELECT.*",
        ".*'.*DROP.*TABLE.*",
        ".*--.*",
        ".*/\\*.*\\*/.*"
    };
    
    for (String pattern : injectionPatterns) {
        if (input.matches(pattern)) {
            return true;
        }
    }
    
    return false;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

#### NoSQL注入防护

```javascript
// mongoSecurity.js - MongoDB安全防护
const mongoose = require('mongoose');
const validator = require('validator');

class MongoSecurityService {
// ❌ 不安全的查询
async getUserUnsafe(req) {
const { userId } = req.body;
return await User.findOne({ _id: userId });
}

// ✅ 安全的查询 - 输入验证
async getUserSafe(req) {
const { userId } = req.body;

// 验证ObjectId格式
if (!mongoose.Types.ObjectId.isValid(userId)) {
throw new Error('Invalid user ID format');
}

return await User.findById(userId);
}

// ✅ 防止NoSQL注入的查询构建
async searchUsers(req) {
const { username, email, age } = req.body;
const query = {};

// 严格的输入验证
if (username) {
if (typeof username !== 'string' || username.length > 50) {
throw new Error('Invalid username');
}
query.username = { $regex: new RegExp(username, 'i') };
}

if (email) {
if (!validator.isEmail(email)) {
throw new Error('Invalid email format');
}
query.email = email;
}

if (age) {
const ageNum = parseInt(age);
if (isNaN(ageNum) || ageNum < 0 || ageNum > 150) {
throw new Error('Invalid age');
}
query.age = ageNum;
}

return await User.find(query).limit(100);
}

// 输入清理中间件
sanitizeInput(req, res, next) {
const sanitize = (obj) => {
if (obj && typeof obj === 'object') {
for (let key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
// 移除MongoDB操作符
if (key.startsWith('$')) {
delete obj[key];
} else {
sanitize(obj[key]);
}
} else if (typeof obj[key] === 'string') {
// 转义特殊字符
obj[key] = obj[key].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
}
}
};

sanitize(req.body);
sanitize(req.query);
sanitize(req.params);

next();
}

// NoSQL注入检测
detectNoSQLInjection(input) {
if (typeof input === 'object' && input !== null) {
const suspiciousKeys = ['$where', '$ne', '$gt', '$lt', '$regex', '$or', '$and'];
for (let key of suspiciousKeys) {
if (input.hasOwnProperty(key)) {
return true;
}
}
}
return false;
}
}

module.exports = MongoSecurityService;

2. 身份验证失效 (Broken Authentication)

JWT安全实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// JwtSecurityService.java - JWT安全服务
@Service
public class JwtSecurityService {

@Value("${jwt.secret}")
private String jwtSecret;

@Value("${jwt.expiration}")
private int jwtExpirationInMs;

private final RedisTemplate<String, String> redisTemplate;
private final Logger logger = LoggerFactory.getLogger(JwtSecurityService.class);

// 生成安全的JWT Token
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", userDetails.getUsername());
claims.put("iat", System.currentTimeMillis() / 1000);
claims.put("jti", UUID.randomUUID().toString()); // JWT ID for revocation

return createToken(claims, userDetails.getUsername());
}

private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + jwtExpirationInMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}

// Token验证
public Boolean validateToken(String token, UserDetails userDetails) {
try {
final String username = getUsernameFromToken(token);
final String jti = getJtiFromToken(token);

// 检查token是否在黑名单中
if (isTokenBlacklisted(jti)) {
logger.warn("Blacklisted token used: {}", jti);
return false;
}

return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
} catch (Exception e) {
logger.error("Token validation failed", e);
return false;
}
}

// 获取用户名
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}

// 获取JWT ID
public String getJtiFromToken(String token) {
return getClaimFromToken(token, claims -> claims.get("jti", String.class));
}

// 获取过期时间
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}

public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}

private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
}

private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}

// Token撤销 - 添加到黑名单
public void revokeToken(String token) {
try {
String jti = getJtiFromToken(token);
Date expiration = getExpirationDateFromToken(token);

// 计算剩余过期时间
long ttl = expiration.getTime() - System.currentTimeMillis();
if (ttl > 0) {
redisTemplate.opsForValue().set(
"blacklist:" + jti,
"revoked",
ttl,
TimeUnit.MILLISECONDS
);
}
} catch (Exception e) {
logger.error("Failed to revoke token", e);
}
}

// 检查token是否在黑名单中
private boolean isTokenBlacklisted(String jti) {
return redisTemplate.hasKey("blacklist:" + jti);
}

// 刷新Token
public String refreshToken(String token) {
try {
final Claims claims = getAllClaimsFromToken(token);
claims.setIssuedAt(new Date());
return createToken(claims, claims.getSubject());
} catch (Exception e) {
logger.error("Failed to refresh token", e);
throw new RuntimeException("Token refresh failed");
}
}
}

多因素认证 (MFA)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// MfaService.java - 多因素认证服务
@Service
public class MfaService {

private final RedisTemplate<String, String> redisTemplate;
private final EmailService emailService;
private final SmsService smsService;

// 生成TOTP密钥
public String generateTotpSecret() {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[20];
random.nextBytes(bytes);
return Base32.encode(bytes);
}

// 验证TOTP代码
public boolean verifyTotpCode(String secret, String code) {
try {
long timeWindow = System.currentTimeMillis() / 30000;

// 检查当前时间窗口和前后各一个窗口(容错)
for (int i = -1; i <= 1; i++) {
String expectedCode = generateTotpCode(secret, timeWindow + i);
if (code.equals(expectedCode)) {
return true;
}
}
return false;
} catch (Exception e) {
return false;
}
}

private String generateTotpCode(String secret, long timeWindow) throws Exception {
byte[] key = Base32.decode(secret);
byte[] data = ByteBuffer.allocate(8).putLong(timeWindow).array();

Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(key, "HmacSHA1"));
byte[] hash = mac.doFinal(data);

int offset = hash[hash.length - 1] & 0x0F;
int code = ((hash[offset] & 0x7F) << 24) |
((hash[offset + 1] & 0xFF) << 16) |
((hash[offset + 2] & 0xFF) << 8) |
(hash[offset + 3] & 0xFF);

return String.format("%06d", code % 1000000);
}

// 发送短信验证码
public void sendSmsCode(String phoneNumber, String userId) {
String code = generateRandomCode(6);
String key = "sms_code:" + userId;

// 存储验证码,5分钟过期
redisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);

// 发送短信
smsService.sendVerificationCode(phoneNumber, code);
}

// 发送邮箱验证码
public void sendEmailCode(String email, String userId) {
String code = generateRandomCode(6);
String key = "email_code:" + userId;

// 存储验证码,10分钟过期
redisTemplate.opsForValue().set(key, code, 10, TimeUnit.MINUTES);

// 发送邮件
emailService.sendVerificationCode(email, code);
}

// 验证短信/邮箱验证码
public boolean verifyCode(String userId, String code, String type) {
String key = type + "_code:" + userId;
String storedCode = redisTemplate.opsForValue().get(key);

if (storedCode != null && storedCode.equals(code)) {
// 验证成功后删除验证码
redisTemplate.delete(key);
return true;
}
return false;
}

private String generateRandomCode(int length) {
SecureRandom random = new SecureRandom();
StringBuilder code = new StringBuilder();
for (int i = 0; i < length; i++) {
code.append(random.nextInt(10));
}
return code.toString();
}

// 生成备用恢复代码
public List<String> generateRecoveryCodes() {
List<String> codes = new ArrayList<>();
SecureRandom random = new SecureRandom();

for (int i = 0; i < 10; i++) {
StringBuilder code = new StringBuilder();
for (int j = 0; j < 8; j++) {
code.append(random.nextInt(10));
}
codes.add(code.toString());
}
return codes;
}
}

3. 敏感数据泄露 (Sensitive Data Exposure)

数据加密服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// EncryptionService.java - 数据加密服务
@Service
public class EncryptionService {

@Value("${encryption.key}")
private String encryptionKey;

private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int GCM_IV_LENGTH = 12;
private static final int GCM_TAG_LENGTH = 16;

// AES-GCM加密
public String encrypt(String plainText) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(encryptionKey.getBytes(), "AES");

byte[] iv = new byte[GCM_IV_LENGTH];
SecureRandom.getInstanceStrong().nextBytes(iv);

Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);

byte[] encryptedText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));

// 将IV和加密数据组合
byte[] encryptedWithIv = new byte[GCM_IV_LENGTH + encryptedText.length];
System.arraycopy(iv, 0, encryptedWithIv, 0, GCM_IV_LENGTH);
System.arraycopy(encryptedText, 0, encryptedWithIv, GCM_IV_LENGTH, encryptedText.length);

return Base64.getEncoder().encodeToString(encryptedWithIv);
}

// AES-GCM解密
public String decrypt(String encryptedText) throws Exception {
byte[] decodedText = Base64.getDecoder().decode(encryptedText);

// 提取IV
byte[] iv = new byte[GCM_IV_LENGTH];
System.arraycopy(decodedText, 0, iv, 0, GCM_IV_LENGTH);

// 提取加密数据
byte[] encrypted = new byte[decodedText.length - GCM_IV_LENGTH];
System.arraycopy(decodedText, GCM_IV_LENGTH, encrypted, 0, encrypted.length);

SecretKeySpec secretKey = new SecretKeySpec(encryptionKey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);

byte[] plainText = cipher.doFinal(encrypted);
return new String(plainText, StandardCharsets.UTF_8);
}

// 密码哈希 - 使用bcrypt
public String hashPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt(12));
}

// 验证密码
public boolean verifyPassword(String password, String hashedPassword) {
return BCrypt.checkpw(password, hashedPassword);
}

// 生成安全的随机盐
public String generateSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
return Base64.getEncoder().encodeToString(salt);
}

// PBKDF2密钥派生
public String deriveKey(String password, String salt, int iterations) throws Exception {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), iterations, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] key = factory.generateSecret(spec).getEncoded();
return Base64.getEncoder().encodeToString(key);
}

// 数据脱敏
public String maskSensitiveData(String data, String type) {
if (data == null || data.isEmpty()) {
return data;
}

switch (type.toLowerCase()) {
case "email":
return maskEmail(data);
case "phone":
return maskPhone(data);
case "creditcard":
return maskCreditCard(data);
case "idcard":
return maskIdCard(data);
default:
return maskDefault(data);
}
}

private String maskEmail(String email) {
int atIndex = email.indexOf('@');
if (atIndex > 0) {
String username = email.substring(0, atIndex);
String domain = email.substring(atIndex);

if (username.length() <= 2) {
return "*".repeat(username.length()) + domain;
} else {
return username.charAt(0) + "*".repeat(username.length() - 2) +
username.charAt(username.length() - 1) + domain;
}
}
return email;
}

private String maskPhone(String phone) {
if (phone.length() >= 7) {
return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
}
return "*".repeat(phone.length());
}

private String maskCreditCard(String cardNumber) {
if (cardNumber.length() >= 8) {
return "*".repeat(cardNumber.length() - 4) + cardNumber.substring(cardNumber.length() - 4);
}
return "*".repeat(cardNumber.length());
}

private String maskIdCard(String idCard) {
if (idCard.length() >= 8) {
return idCard.substring(0, 4) + "*".repeat(idCard.length() - 8) +
idCard.substring(idCard.length() - 4);
}
return "*".repeat(idCard.length());
}

private String maskDefault(String data) {
if (data.length() <= 2) {
return "*".repeat(data.length());
} else if (data.length() <= 4) {
return data.charAt(0) + "*".repeat(data.length() - 2) + data.charAt(data.length() - 1);
} else {
return data.substring(0, 2) + "*".repeat(data.length() - 4) +
data.substring(data.length() - 2);
}
}
}

4. XML外部实体注入 (XXE)

XXE防护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// XmlSecurityService.java - XML安全处理服务
@Service
public class XmlSecurityService {

private final Logger logger = LoggerFactory.getLogger(XmlSecurityService.class);

// 安全的XML解析器配置
public DocumentBuilder createSecureDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

// 禁用外部实体
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

// 禁用XInclude
factory.setXIncludeAware(false);

// 禁用扩展功能
factory.setExpandEntityReferences(false);

return factory.newDocumentBuilder();
}

// 安全的SAX解析器配置
public SAXParser createSecureSAXParser() throws ParserConfigurationException, SAXException {
SAXParserFactory factory = SAXParserFactory.newInstance();

// 禁用外部实体
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

return factory.newSAXParser();
}

// 安全的XML解析
public Document parseXmlSafely(String xmlContent) throws Exception {
try {
DocumentBuilder builder = createSecureDocumentBuilder();

// 设置自定义EntityResolver来阻止外部实体
builder.setEntityResolver(new SecureEntityResolver());

return builder.parse(new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
logger.error("XML parsing failed", e);
throw new SecurityException("Invalid XML content");
}
}

// 安全的EntityResolver
private static class SecureEntityResolver implements EntityResolver {
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
// 阻止所有外部实体解析
throw new SAXException("External entity access is not allowed: " + systemId);
}
}

// XML内容验证
public boolean isValidXmlContent(String xmlContent) {
if (xmlContent == null || xmlContent.trim().isEmpty()) {
return false;
}

// 检查是否包含危险的XML结构
String[] dangerousPatterns = {
"<!DOCTYPE",
"<!ENTITY",
"SYSTEM",
"PUBLIC",
"&[a-zA-Z]",
"file://",
"http://",
"https://",
"ftp://"
};

String upperXml = xmlContent.toUpperCase();
for (String pattern : dangerousPatterns) {
if (upperXml.contains(pattern.toUpperCase())) {
logger.warn("Dangerous XML pattern detected: {}", pattern);
return false;
}
}

return true;
}

// XML内容清理
public String sanitizeXmlContent(String xmlContent) {
if (xmlContent == null) {
return null;
}

// 移除DOCTYPE声明
xmlContent = xmlContent.replaceAll("<!DOCTYPE[^>]*>", "");

// 移除ENTITY声明
xmlContent = xmlContent.replaceAll("<!ENTITY[^>]*>", "");

// 移除外部引用
xmlContent = xmlContent.replaceAll("SYSTEM\\s+[\"'][^\"']*[\"']", "");
xmlContent = xmlContent.replaceAll("PUBLIC\\s+[\"'][^\"']*[\"']\\s+[\"'][^\"']*[\"']", "");

return xmlContent;
}

// 使用Jackson安全解析XML
public <T> T parseXmlWithJackson(String xmlContent, Class<T> valueType) throws Exception {
XmlMapper xmlMapper = new XmlMapper();

// 配置安全设置
xmlMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true);
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

// 禁用外部实体处理
XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);

xmlMapper.getFactory().setXMLInputFactory(xmlInputFactory);

return xmlMapper.readValue(xmlContent, valueType);
}
}

5. 访问控制缺陷 (Broken Access Control)

RBAC权限控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// RbacService.java - 基于角色的访问控制服务
@Service
public class RbacService {

@Autowired
private UserRepository userRepository;

@Autowired
private RoleRepository roleRepository;

@Autowired
private PermissionRepository permissionRepository;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

private final Logger logger = LoggerFactory.getLogger(RbacService.class);

// 检查用户权限
public boolean hasPermission(String userId, String resource, String action) {
try {
// 先从缓存中获取
String cacheKey = "user_permissions:" + userId;
Set<String> permissions = (Set<String>) redisTemplate.opsForValue().get(cacheKey);

if (permissions == null) {
permissions = getUserPermissions(userId);
// 缓存30分钟
redisTemplate.opsForValue().set(cacheKey, permissions, 30, TimeUnit.MINUTES);
}

String requiredPermission = resource + ":" + action;
return permissions.contains(requiredPermission) || permissions.contains("*:*");

} catch (Exception e) {
logger.error("Permission check failed for user: {}", userId, e);
return false;
}
}

// 获取用户所有权限
private Set<String> getUserPermissions(String userId) {
Set<String> permissions = new HashSet<>();

User user = userRepository.findById(userId).orElse(null);
if (user == null) {
return permissions;
}

// 获取用户角色
Set<Role> roles = user.getRoles();
for (Role role : roles) {
// 获取角色权限
Set<Permission> rolePermissions = role.getPermissions();
for (Permission permission : rolePermissions) {
permissions.add(permission.getResource() + ":" + permission.getAction());
}
}

return permissions;
}

// 检查资源所有权
public boolean isResourceOwner(String userId, String resourceType, String resourceId) {
try {
switch (resourceType.toLowerCase()) {
case "user":
return userId.equals(resourceId);
case "document":
return isDocumentOwner(userId, resourceId);
case "project":
return isProjectMember(userId, resourceId);
default:
return false;
}
} catch (Exception e) {
logger.error("Resource ownership check failed", e);
return false;
}
}

private boolean isDocumentOwner(String userId, String documentId) {
// 实现文档所有权检查逻辑
return documentRepository.existsByIdAndOwnerId(documentId, userId);
}

private boolean isProjectMember(String userId, String projectId) {
// 实现项目成员检查逻辑
return projectMemberRepository.existsByProjectIdAndUserId(projectId, userId);
}

// 权限注解处理
@PreAuthorize("@rbacService.hasPermission(authentication.name, #resource, #action)")
public void checkPermission(String resource, String action) {
// 由Spring Security的@PreAuthorize注解处理
}

// 动态权限检查
public boolean checkDynamicPermission(String userId, String resource, String action, Map<String, Object> context) {
// 基础权限检查
if (!hasPermission(userId, resource, action)) {
return false;
}

// 动态条件检查
return evaluateDynamicConditions(userId, resource, context);
}

private boolean evaluateDynamicConditions(String userId, String resource, Map<String, Object> context) {
// 时间限制
if (context.containsKey("timeRestriction")) {
LocalTime now = LocalTime.now();
LocalTime startTime = (LocalTime) context.get("startTime");
LocalTime endTime = (LocalTime) context.get("endTime");

if (now.isBefore(startTime) || now.isAfter(endTime)) {
return false;
}
}

// IP限制
if (context.containsKey("ipRestriction")) {
String userIp = (String) context.get("userIp");
List<String> allowedIps = (List<String>) context.get("allowedIps");

if (!allowedIps.contains(userIp)) {
return false;
}
}

// 地理位置限制
if (context.containsKey("locationRestriction")) {
String userCountry = (String) context.get("userCountry");
List<String> allowedCountries = (List<String>) context.get("allowedCountries");

if (!allowedCountries.contains(userCountry)) {
return false;
}
}

return true;
}

// 清除用户权限缓存
public void clearUserPermissionCache(String userId) {
String cacheKey = "user_permissions:" + userId;
redisTemplate.delete(cacheKey);
}

// 批量权限检查
public Map<String, Boolean> batchCheckPermissions(String userId, List<String> permissions) {
Map<String, Boolean> results = new HashMap<>();

for (String permission : permissions) {
String[] parts = permission.split(":");
if (parts.length == 2) {
results.put(permission, hasPermission(userId, parts[0], parts[1]));
} else {
results.put(permission, false);
}
}

return results;
}
}

API访问控制中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// accessControl.js - API访问控制中间件
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
const slowDown = require('express-slow-down');

class AccessControlMiddleware {
constructor(rbacService, redisClient) {
this.rbacService = rbacService;
this.redisClient = redisClient;
}

// JWT认证中间件
authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];

if (!token) {
return res.status(401).json({
success: false,
message: 'Access token required'
});
}

jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({
success: false,
message: 'Invalid or expired token'
});
}

req.user = user;
next();
});
}

// 权限检查中间件
requirePermission(resource, action) {
return async (req, res, next) => {
try {
const userId = req.user.id;
const hasPermission = await this.rbacService.hasPermission(userId, resource, action);

if (!hasPermission) {
return res.status(403).json({
success: false,
message: 'Insufficient permissions'
});
}

next();
} catch (error) {
console.error('Permission check error:', error);
return res.status(500).json({
success: false,
message: 'Permission check failed'
});
}
};
}

// 资源所有权检查
requireOwnership(resourceType, resourceIdParam = 'id') {
return async (req, res, next) => {
try {
const userId = req.user.id;
const resourceId = req.params[resourceIdParam];

const isOwner = await this.rbacService.isResourceOwner(userId, resourceType, resourceId);

if (!isOwner) {
return res.status(403).json({
success: false,
message: 'Access denied: not resource owner'
});
}

next();
} catch (error) {
console.error('Ownership check error:', error);
return res.status(500).json({
success: false,
message: 'Ownership check failed'
});
}
};
}

// 速率限制
createRateLimit(windowMs = 15 * 60 * 1000, max = 100) {
return rateLimit({
windowMs,
max,
message: {
success: false,
message: 'Too many requests, please try again later'
},
standardHeaders: true,
legacyHeaders: false,
store: new rateLimit.RedisStore({
client: this.redisClient,
prefix: 'rl:'
})
});
}

// 请求减速
createSlowDown(windowMs = 15 * 60 * 1000, delayAfter = 50) {
return slowDown({
windowMs,
delayAfter,
delayMs: 500,
maxDelayMs: 20000,
store: new slowDown.RedisStore({
client: this.redisClient,
prefix: 'sd:'
})
});
}

// IP白名单检查
requireWhitelistedIP(whitelist) {
return (req, res, next) => {
const clientIP = req.ip || req.connection.remoteAddress;

if (!whitelist.includes(clientIP)) {
return res.status(403).json({
success: false,
message: 'Access denied: IP not whitelisted'
});
}

next();
};
}

// 时间窗口访问控制
requireTimeWindow(startHour, endHour) {
return (req, res, next) => {
const now = new Date();
const currentHour = now.getHours();

if (currentHour < startHour || currentHour >= endHour) {
return res.status(403).json({
success: false,
message: 'Access denied: outside allowed time window'
});
}

next();
};
}

// 地理位置限制
requireGeoLocation(allowedCountries) {
return (req, res, next) => {
const country = req.headers['cf-ipcountry'] || req.headers['x-country-code'];

if (!country || !allowedCountries.includes(country)) {
return res.status(403).json({
success: false,
message: 'Access denied: geographic restriction'
});
}

next();
};
}

// API密钥验证
requireApiKey() {
return async (req, res, next) => {
const apiKey = req.headers['x-api-key'];

if (!apiKey) {
return res.status(401).json({
success: false,
message: 'API key required'
});
}

try {
// 从Redis检查API密钥
const keyData = await this.redisClient.get(`api_key:${apiKey}`);

if (!keyData) {
return res.status(401).json({
success: false,
message: 'Invalid API key'
});
}

const keyInfo = JSON.parse(keyData);
req.apiKey = keyInfo;
next();
} catch (error) {
console.error('API key validation error:', error);
return res.status(500).json({
success: false,
message: 'API key validation failed'
});
}
};
}
}

module.exports = AccessControlMiddleware;

6. 安全配置错误 (Security Misconfiguration)

安全配置检查工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// SecurityConfigurationChecker.java - 安全配置检查工具
@Component
public class SecurityConfigurationChecker {

private final Logger logger = LoggerFactory.getLogger(SecurityConfigurationChecker.class);

@Value("${server.port:8080}")
private int serverPort;

@Value("${server.ssl.enabled:false}")
private boolean sslEnabled;

@PostConstruct
public void checkSecurityConfiguration() {
List<String> issues = new ArrayList<>();

// 检查HTTPS配置
if (!sslEnabled) {
issues.add("HTTPS is not enabled - sensitive data may be transmitted in plain text");
}

// 检查默认端口
if (serverPort == 8080 || serverPort == 80) {
issues.add("Using default port " + serverPort + " - consider using a non-standard port");
}

// 检查环境变量
checkEnvironmentVariables(issues);

// 检查安全头配置
checkSecurityHeaders(issues);

// 检查数据库配置
checkDatabaseConfiguration(issues);

// 输出检查结果
if (!issues.isEmpty()) {
logger.warn("Security configuration issues found:");
issues.forEach(issue -> logger.warn("- {}", issue));
} else {
logger.info("Security configuration check passed");
}
}

private void checkEnvironmentVariables(List<String> issues) {
String[] sensitiveVars = {
"JWT_SECRET", "DATABASE_PASSWORD", "REDIS_PASSWORD",
"API_KEY", "ENCRYPTION_KEY"
};

for (String var : sensitiveVars) {
String value = System.getenv(var);
if (value == null || value.isEmpty()) {
issues.add("Environment variable " + var + " is not set");
} else if (value.length() < 32) {
issues.add("Environment variable " + var + " is too short (< 32 characters)");
}
}
}

private void checkSecurityHeaders(List<String> issues) {
// 这里可以检查Spring Security配置是否包含必要的安全头
// 实际实现中需要检查SecurityConfig类的配置
}

private void checkDatabaseConfiguration(List<String> issues) {
String dbUrl = System.getenv("DATABASE_URL");
if (dbUrl != null) {
if (dbUrl.contains("localhost") || dbUrl.contains("127.0.0.1")) {
issues.add("Database URL contains localhost - ensure proper network security");
}
if (!dbUrl.contains("ssl=true") && !dbUrl.contains("sslmode=require")) {
issues.add("Database connection may not use SSL");
}
}
}

// 运行时安全检查
@Scheduled(fixedRate = 3600000) // 每小时检查一次
public void performRuntimeSecurityCheck() {
checkForSuspiciousActivity();
checkSystemResources();
checkLogFiles();
}

private void checkForSuspiciousActivity() {
// 检查异常登录尝试
// 检查异常API调用
// 检查资源使用异常
}

private void checkSystemResources() {
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;

double memoryUsage = (double) usedMemory / maxMemory * 100;

if (memoryUsage > 90) {
logger.warn("High memory usage detected: {}%", String.format("%.2f", memoryUsage));
}
}

private void checkLogFiles() {
// 检查日志文件大小
// 检查错误日志中的异常模式
// 检查访问日志中的可疑请求
}
}

安全配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// SecurityConfig.java - Spring Security安全配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Autowired
private JwtRequestFilter jwtRequestFilter;

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}

@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/actuator/health").permitAll()
.anyRequest().authenticated()
)
.exceptionHandling(ex ->
ex.authenticationEntryPoint(jwtAuthenticationEntryPoint))
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

// 添加安全头
http.headers(headers -> headers
.frameOptions().deny()
.contentTypeOptions().and()
.httpStrictTransportSecurity(hstsConfig -> hstsConfig
.maxAgeInSeconds(31536000)
.includeSubdomains(true))
.and()
.addHeaderWriter(new StaticHeadersWriter("X-Content-Type-Options", "nosniff"))
.addHeaderWriter(new StaticHeadersWriter("X-Frame-Options", "DENY"))
.addHeaderWriter(new StaticHeadersWriter("X-XSS-Protection", "1; mode=block"))
.addHeaderWriter(new StaticHeadersWriter("Referrer-Policy", "strict-origin-when-cross-origin"))
.addHeaderWriter(new StaticHeadersWriter("Permissions-Policy",
"geolocation=(), microphone=(), camera=()"))
.addHeaderWriter(new StaticHeadersWriter("Content-Security-Policy",
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"))
);

return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("https://*.yourdomain.com"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
}

7. 跨站脚本攻击 (XSS)

XSS防护服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// XssProtectionService.java - XSS防护服务
@Service
public class XssProtectionService {

private final Logger logger = LoggerFactory.getLogger(XssProtectionService.class);

// HTML编码
public String encodeHtml(String input) {
if (input == null) {
return null;
}
return StringEscapeUtils.escapeHtml4(input);
}

// JavaScript编码
public String encodeJavaScript(String input) {
if (input == null) {
return null;
}
return StringEscapeUtils.escapeEcmaScript(input);
}

// URL编码
public String encodeUrl(String input) {
if (input == null) {
return null;
}
try {
return URLEncoder.encode(input, StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
logger.error("URL encoding failed", e);
return input;
}
}

// CSS编码
public String encodeCss(String input) {
if (input == null) {
return null;
}

StringBuilder encoded = new StringBuilder();
for (char c : input.toCharArray()) {
if (Character.isLetterOrDigit(c)) {
encoded.append(c);
} else {
encoded.append("\\").append(Integer.toHexString(c)).append(" ");
}
}
return encoded.toString();
}

// HTML清理 - 使用OWASP Java HTML Sanitizer
public String sanitizeHtml(String input) {
if (input == null) {
return null;
}

PolicyFactory policy = Sanitizers.FORMATTING
.and(Sanitizers.LINKS)
.and(Sanitizers.BLOCKS)
.and(Sanitizers.IMAGES)
.and(Sanitizers.STYLES);

return policy.sanitize(input);
}

// 严格的HTML清理 - 只允许基本标签
public String sanitizeHtmlStrict(String input) {
if (input == null) {
return null;
}

PolicyFactory policy = new HtmlPolicyBuilder()
.allowElements("p", "br", "strong", "em", "u", "i", "b")
.allowAttributes("class").onElements("p")
.toFactory();

return policy.sanitize(input);
}

// XSS检测
public boolean containsXss(String input) {
if (input == null) {
return false;
}

String[] xssPatterns = {
"<script[^>]*>.*?</script>",
"javascript:",
"vbscript:",
"onload\\s*=",
"onerror\\s*=",
"onclick\\s*=",
"onmouseover\\s*=",
"onfocus\\s*=",
"onblur\\s*=",
"<iframe[^>]*>",
"<object[^>]*>",
"<embed[^>]*>",
"<link[^>]*>",
"<meta[^>]*>",
"expression\\s*\\(",
"url\\s*\\(",
"@import",
"\\\\x[0-9a-fA-F]{2}",
"&#x[0-9a-fA-F]+;",
"&#[0-9]+;"
};

String lowerInput = input.toLowerCase();
for (String pattern : xssPatterns) {
if (lowerInput.matches(".*" + pattern + ".*")) {
logger.warn("XSS pattern detected: {}", pattern);
return true;
}
}

return false;
}

// 内容安全策略生成
public String generateCSP() {
return "default-src 'self'; " +
"script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; " +
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " +
"font-src 'self' https://fonts.gstatic.com; " +
"img-src 'self' data: https:; " +
"connect-src 'self'; " +
"frame-ancestors 'none'; " +
"base-uri 'self'; " +
"form-action 'self'";
}

// 输入验证和清理中间件
public String validateAndSanitizeInput(String input, String inputType) {
if (input == null) {
return null;
}

// 长度检查
if (input.length() > 10000) {
throw new IllegalArgumentException("Input too long");
}

// XSS检测
if (containsXss(input)) {
throw new SecurityException("XSS content detected");
}

// 根据输入类型进行相应处理
switch (inputType.toLowerCase()) {
case "html":
return sanitizeHtml(input);
case "text":
return encodeHtml(input);
case "url":
return encodeUrl(input);
case "javascript":
return encodeJavaScript(input);
case "css":
return encodeCss(input);
default:
return encodeHtml(input);
}
}
}

前端XSS防护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// xssProtection.js - 前端XSS防护工具
class XSSProtection {

// HTML编码
static encodeHTML(str) {
if (typeof str !== 'string') return str;

const htmlEntities = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;'
};

return str.replace(/[&<>"'/]/g, (match) => htmlEntities[match]);
}

// HTML解码
static decodeHTML(str) {
if (typeof str !== 'string') return str;

const htmlEntities = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#x27;': "'",
'&#x2F;': '/'
};

return str.replace(/&amp;|&lt;|&gt;|&quot;|&#x27;|&#x2F;/g, (match) => htmlEntities[match]);
}

// JavaScript编码
static encodeJS(str) {
if (typeof str !== 'string') return str;

return str.replace(/[\\"']/g, '\\$&')
.replace(/\u0000/g, '\\0')
.replace(/\u000A/g, '\\n')
.replace(/\u000D/g, '\\r')
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
}

// URL编码
static encodeURL(str) {
if (typeof str !== 'string') return str;
return encodeURIComponent(str);
}

// CSS编码
static encodeCSS(str) {
if (typeof str !== 'string') return str;

return str.replace(/[^a-zA-Z0-9]/g, (match) => {
const code = match.charCodeAt(0);
return '\\' + code.toString(16) + ' ';
});
}

// 安全的innerHTML设置
static safeInnerHTML(element, html) {
if (!element || typeof html !== 'string') return;

// 创建临时元素进行清理
const temp = document.createElement('div');
temp.innerHTML = this.sanitizeHTML(html);

// 移除所有脚本标签
const scripts = temp.querySelectorAll('script');
scripts.forEach(script => script.remove());

// 移除危险属性
const allElements = temp.querySelectorAll('*');
allElements.forEach(el => {
const dangerousAttrs = ['onclick', 'onload', 'onerror', 'onmouseover', 'onfocus', 'onblur'];
dangerousAttrs.forEach(attr => {
if (el.hasAttribute(attr)) {
el.removeAttribute(attr);
}
});

// 检查href和src属性
if (el.hasAttribute('href')) {
const href = el.getAttribute('href');
if (href.startsWith('javascript:') || href.startsWith('vbscript:')) {
el.removeAttribute('href');
}
}

if (el.hasAttribute('src')) {
const src = el.getAttribute('src');
if (src.startsWith('javascript:') || src.startsWith('vbscript:')) {
el.removeAttribute('src');
}
}
});

element.innerHTML = temp.innerHTML;
}

// HTML清理
static sanitizeHTML(html) {
if (typeof html !== 'string') return '';

// 允许的标签
const allowedTags = ['p', 'br', 'strong', 'em', 'u', 'i', 'b', 'a', 'ul', 'ol', 'li'];

// 移除不允许的标签
let sanitized = html.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>/g, (match, tag) => {
if (allowedTags.includes(tag.toLowerCase())) {
return match;
}
return '';
});

// 移除脚本内容
sanitized = sanitized.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');

// 移除样式内容
sanitized = sanitized.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '');

return sanitized;
}

// XSS检测
static detectXSS(input) {
if (typeof input !== 'string') return false;

const xssPatterns = [
/<script[^>]*>.*?<\/script>/gi,
/javascript:/gi,
/vbscript:/gi,
/on\w+\s*=/gi,
/<iframe[^>]*>/gi,
/<object[^>]*>/gi,
/<embed[^>]*>/gi,
/expression\s*\(/gi,
/url\s*\(/gi,
/@import/gi
];

return xssPatterns.some(pattern => pattern.test(input));
}

// 安全的事件监听器
static addSafeEventListener(element, event, handler) {
if (!element || typeof handler !== 'function') return;

const safeHandler = (e) => {
try {
// 验证事件来源
if (e.isTrusted === false) {
console.warn('Untrusted event detected');
return;
}

handler(e);
} catch (error) {
console.error('Event handler error:', error);
}
};

element.addEventListener(event, safeHandler);
}

// 内容安全策略检查
static checkCSP() {
const metaTags = document.querySelectorAll('meta[http-equiv="Content-Security-Policy"]');

if (metaTags.length === 0) {
console.warn('No Content Security Policy found');
return false;
}

const csp = metaTags[0].getAttribute('content');

// 检查基本CSP指令
const requiredDirectives = ['default-src', 'script-src', 'style-src'];
const hasRequired = requiredDirectives.every(directive =>
csp.includes(directive)
);

if (!hasRequired) {
console.warn('CSP missing required directives');
return false;
}

return true;
}
}

// 自动初始化XSS防护
document.addEventListener('DOMContentLoaded', () => {
// 检查CSP
XSSProtection.checkCSP();

// 监控动态内容插入
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
// 检查新添加的元素是否包含危险内容
const scripts = node.querySelectorAll('script');
if (scripts.length > 0) {
console.warn('Script tag detected in dynamic content');
}
}
});
});
});

observer.observe(document.body, {
childList: true,
subtree: true
});
});

// 导出XSS防护类
if (typeof module !== 'undefined' && module.exports) {
module.exports = XSSProtection;
} else if (typeof window !== 'undefined') {
window.XSSProtection = XSSProtection;
}

8. 不安全的反序列化 (Insecure Deserialization)

安全反序列化服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
// SecureSerializationService.java - 安全序列化服务
@Service
public class SecureSerializationService {

private final Logger logger = LoggerFactory.getLogger(SecureSerializationService.class);
private final ObjectMapper objectMapper;

public SecureSerializationService() {
this.objectMapper = new ObjectMapper();
configureObjectMapper();
}

private void configureObjectMapper() {
// 禁用默认类型信息
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

// 启用安全特性
objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

// 禁用多态类型处理
objectMapper.disable(MapperFeature.USE_ANNOTATIONS);

// 设置类型验证
objectMapper.setPolymorphicTypeValidator(BasicPolymorphicTypeValidator.builder()
.allowIfSubType(Object.class)
.build());
}

// 安全的JSON序列化
public String serializeToJson(Object object) throws Exception {
try {
return objectMapper.writeValueAsString(object);
} catch (Exception e) {
logger.error("JSON serialization failed", e);
throw new SerializationException("Serialization failed", e);
}
}

// 安全的JSON反序列化
public <T> T deserializeFromJson(String json, Class<T> clazz) throws Exception {
try {
// 验证JSON格式
if (!isValidJson(json)) {
throw new IllegalArgumentException("Invalid JSON format");
}

// 检查类型安全
if (!isAllowedClass(clazz)) {
throw new SecurityException("Deserialization of class " + clazz.getName() + " is not allowed");
}

return objectMapper.readValue(json, clazz);
} catch (Exception e) {
logger.error("JSON deserialization failed for class: {}", clazz.getName(), e);
throw new DeserializationException("Deserialization failed", e);
}
}

// 带类型引用的反序列化
public <T> T deserializeFromJson(String json, TypeReference<T> typeRef) throws Exception {
try {
if (!isValidJson(json)) {
throw new IllegalArgumentException("Invalid JSON format");
}

return objectMapper.readValue(json, typeRef);
} catch (Exception e) {
logger.error("JSON deserialization failed for type reference", e);
throw new DeserializationException("Deserialization failed", e);
}
}

// 验证JSON格式
private boolean isValidJson(String json) {
if (json == null || json.trim().isEmpty()) {
return false;
}

try {
objectMapper.readTree(json);
return true;
} catch (Exception e) {
return false;
}
}

// 检查允许的类
private boolean isAllowedClass(Class<?> clazz) {
// 白名单机制
String[] allowedPackages = {
"com.yourcompany.model",
"com.yourcompany.dto",
"java.lang",
"java.util",
"java.time"
};

String className = clazz.getName();

// 检查是否在允许的包中
for (String allowedPackage : allowedPackages) {
if (className.startsWith(allowedPackage)) {
return true;
}
}

// 黑名单检查
String[] blacklistedClasses = {
"java.lang.Runtime",
"java.lang.ProcessBuilder",
"java.io.FileInputStream",
"java.io.FileOutputStream",
"java.net.Socket",
"java.rmi",
"javax.script",
"org.springframework.context.support.ClassPathXmlApplicationContext"
};

for (String blacklisted : blacklistedClasses) {
if (className.startsWith(blacklisted)) {
logger.warn("Attempted deserialization of blacklisted class: {}", className);
return false;
}
}

return false;
}

// 安全的Java对象序列化(不推荐使用)
public byte[] serializeObject(Serializable object) throws Exception {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {

oos.writeObject(object);
return baos.toByteArray();
} catch (Exception e) {
logger.error("Object serialization failed", e);
throw new SerializationException("Object serialization failed", e);
}
}

// 安全的Java对象反序列化(不推荐使用)
public Object deserializeObject(byte[] data) throws Exception {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
SecureObjectInputStream ois = new SecureObjectInputStream(bais)) {

return ois.readObject();
} catch (Exception e) {
logger.error("Object deserialization failed", e);
throw new DeserializationException("Object deserialization failed", e);
}
}

// 自定义安全的ObjectInputStream
private static class SecureObjectInputStream extends ObjectInputStream {

public SecureObjectInputStream(InputStream in) throws IOException {
super(in);
}

@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName();

// 检查类名是否在白名单中
if (!isClassAllowed(className)) {
throw new InvalidClassException("Deserialization of class " + className + " is not allowed");
}

return super.resolveClass(desc);
}

private boolean isClassAllowed(String className) {
// 实现与上面相同的白名单/黑名单逻辑
String[] allowedPackages = {
"com.yourcompany.model",
"java.lang.String",
"java.lang.Integer",
"java.lang.Long",
"java.lang.Boolean",
"java.util.ArrayList",
"java.util.HashMap"
};

for (String allowed : allowedPackages) {
if (className.startsWith(allowed)) {
return true;
}
}

return false;
}
}

// 数据完整性验证
public String signData(String data, String secretKey) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
mac.init(keySpec);

byte[] signature = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signature);
}

// 验证数据签名
public boolean verifyDataSignature(String data, String signature, String secretKey) throws Exception {
String expectedSignature = signData(data, secretKey);
return MessageDigest.isEqual(
signature.getBytes(StandardCharsets.UTF_8),
expectedSignature.getBytes(StandardCharsets.UTF_8)
);
}

// 安全的序列化数据传输
public String createSecurePayload(Object data, String secretKey) throws Exception {
// 序列化数据
String jsonData = serializeToJson(data);

// 创建时间戳
long timestamp = System.currentTimeMillis();

// 创建载荷
Map<String, Object> payload = new HashMap<>();
payload.put("data", jsonData);
payload.put("timestamp", timestamp);

String payloadJson = objectMapper.writeValueAsString(payload);

// 签名
String signature = signData(payloadJson, secretKey);

// 最终载荷
Map<String, String> finalPayload = new HashMap<>();
finalPayload.put("payload", payloadJson);
finalPayload.put("signature", signature);

return objectMapper.writeValueAsString(finalPayload);
}

// 验证并解析安全载荷
public <T> T parseSecurePayload(String securePayload, String secretKey, Class<T> dataClass) throws Exception {
// 解析最终载荷
Map<String, String> finalPayload = objectMapper.readValue(securePayload,
new TypeReference<Map<String, String>>() {});

String payloadJson = finalPayload.get("payload");
String signature = finalPayload.get("signature");

// 验证签名
if (!verifyDataSignature(payloadJson, signature, secretKey)) {
throw new SecurityException("Invalid payload signature");
}

// 解析载荷
Map<String, Object> payload = objectMapper.readValue(payloadJson,
new TypeReference<Map<String, Object>>() {});

// 检查时间戳(防重放攻击)
long timestamp = ((Number) payload.get("timestamp")).longValue();
long currentTime = System.currentTimeMillis();

if (currentTime - timestamp > 300000) { // 5分钟过期
throw new SecurityException("Payload expired");
}

// 反序列化数据
String jsonData = (String) payload.get("data");
return deserializeFromJson(jsonData, dataClass);
}
}