第二部分:安全调优
安全调优的目标是减少系统的攻击面,遵循最小权限原则,并对常见漏洞进行防御。
一. 输入验证与输出编码
1.1 防止 SQL 注入
绝对不要使用字符串拼接 SQL!
// 高危:拼接输入
String name = request.getParameter("name");
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
// 输入:' OR '1'='1 --> 全表查询
// 正确:使用预编译语句(PreparedStatement)或 ORM 框架
String safeSql = "SELECT * FROM users WHERE name = ?";
PreparedStatement ps = connection.prepareStatement(safeSql);
ps.setString(1, name);
ResultSet rs = ps.executeQuery();
// 使用 MyBatis 时注意 #{} 和 ${} 的区别
// 正确:#{name} 错误:${name} (仍可能注入,仅用于动态表名/列名)
1.2 防止 XSS (跨站脚本攻击)
输出编码(Output Encoding)
// 使用 OWASP Java Encoder 对 HTML 上下文编码
import org.owasp.encoder.Encode;
String userInput = request.getParameter("comment");
// 输出到 HTML 中
response.getWriter().write(Encode.forHtml(userInput));
// 输出到 JavaScript 字符串中
response.getWriter().write(Encode.forJavaScript(userInput));
// 输出到 URL 参数中
String url = "/search?q=" + Encode.forUriComponent(userInput);
使用 Spring 的 HtmlUtils 或 Thymeleaf 默认自动转义,但要避免使用 th:utext(未转义文本)。
1.3 防止命令注入
避免 Runtime.exec() 或 ProcessBuilder 直接拼接用户输入。如果必须调用外部命令,使用白名单校验。
// 危险:用户输入 "127.0.0.1; rm -rf /"
String ip = request.getParameter("ip");
Runtime.getRuntime().exec("ping " + ip);
// 安全:使用正则校验 IP 格式
Pattern ipPattern = Pattern.compile("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.");
if (ipPattern.matcher(ip).matches()) {
// 校验通过后再执行
}
二. 身份认证与会话管理
2.1 JWT 安全使用
使用强密钥(HS256 密钥长度 ≥ 256 bits,RS256 密钥长度 ≥ 2048 bits)
设置合理的 wap588.net 过期时间(exp)
禁止将敏感信息如密码存入 JWT
防止算法混淆攻击(如果服务端接受 none 算法或允许修改 alg)
// JWT 生成(使用 Nimbus JOSE + JWT 库)
JWSSigner signer = new MACSigner(secretKeyBytes);
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.subject(userId)
.issueTime(new Date())
.expirationTime(new Date(System.currentTimeMillis() + 3600000)) // 1 小时
.claim("role", "USER")
.build();
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claims);
signedJWT.sign(signer);
String token = signedJWT.serialize();
2.2 会话固定攻击防护
登录成功后必须生成新的会话 ID。
// Servlet 中
HttpSession oldSession = request.getSession(false);
if (oldSession != null) {
oldSession.invalidate(); // 销毁原会话
}
HttpSession newSession = request.getSession(true); // 创建新会话
Spring Security 默认已防护(sessionManagement().sessionFixation().migrateSession())。
2.3 密码存储
使用 bcrypt、scrypt 或 PBKDF2 加盐哈希,禁止 MD5/SHA1。
bcrypt 内置盐且计算慢,可防御彩虹表和暴力破解。
// Spring Security BCryptPasswordEncoder
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10); // strength=10 迭代次数 2^10
String encodedPassword = encoder.encode("userPassword");
boolean matches = encoder.matches("rawPassword", encodedPassword);