性能与安全调优进阶指南:软件开发最佳实践(2025第四部分)
软件项目进入交付阶段,性能与安全调优往往直接决定系统能否稳定上线。前面重点梳理了性能优化策略,现在必须聚焦另一个影响根本的环节——安全加固。安全调优的核心逻辑在于收缩攻击面、实施最小权限原则、并针对常见漏洞预置防御机制。
具体落地可以从以下几个关键维度切入。
一. 输入验证与输出编码
1.1 防止 SQL 注入
一条硬性规则:永远不要通过字符串拼接构造SQL语句。这是新手最常踩、也是最危险的陷阱。
直接看一个典型反例:
// 高危:拼接输入
String name = request.getParameter("name");
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
若用户输入 ' OR '1'='1,这条SQL会直接变成全表扫描。如果用在登录验证场景,后果可想而知。
正确的做法是采用预编译语句PreparedStatement:
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 (跨站脚本攻击)
XSS攻击的本质是用户数据被错误解释为可执行代码。防御核心就是四个字:输出编码。
借助OWASP Java Encoder库可以实现精细的上下文编码:
import org.owasp.encoder.Encode;
// 输出到 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默认均自动转义,极大降低了风险。但有一个关键注意点:尽量避免使用Thymeleaf的 th:utext,它输出未转义内容,等于为XSS敞开了大门。
1.3 防止命令注入
后台工具类功能常需调用系统命令,比如Ping操作。最容易出错的地方是让用户输入直接拼接进命令字符串。
Runtime.exec() 或 ProcessBuilder 一旦直接拼接用户输入,风险极高。例如输入 "127.0.0.1; rm -rf /",后果可能是灾难性的。
安全做法是先做白名单校验,用正则限制输入格式:
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 安全使用
JWT用对是利器,用错则是漏洞入口。以下几个要点必须刻在脑子里:
- 密钥强度要达标:HS256至少256位,RS256至少2048位。
- 过期时间必须设:合理设置exp字段,防止token永久有效。
- 敏感信息不进payload:密码、密钥等严禁放入JWT。
- 防范算法混淆攻击:服务端务必严格校验签名算法,拒绝
none算法,否则攻击者可伪造任意token。
下面是一个符合规范的使用Nimbus库生成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,用户登录后,攻击者直接利用该ID冒充身份。防御手段很简单——登录成功后强制创建新会话,销毁旧的。
// Servlet 中
HttpSession oldSession = request.getSession(false);
if (oldSession != null) {
oldSession.invalidate(); // 销毁原会话
}
HttpSession newSession = request.getSession(true); // 创建新会话
如果使用Spring Security,它默认已经做好防护,sessionManagement().sessionFixation().migrateSession() 正是负责此功能的配置。
2.3 密码存储
密码存储方案早已没有争议。MD5、SHA1这类快速哈希算法必须淘汰。行业公认的安全方案是bcrypt、scrypt或PBKDF2等带有盐值的慢哈希算法。
bcrypt内置随机盐,且计算速度慢——这正是其优势,能有效抵御彩虹表和暴力破解。以Spring Security的 BCryptPasswordEncoder 为例:
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10); // strength=10 迭代次数 2^10
String encodedPassword = encoder.encode("userPassword");
boolean matches = encoder.matches("rawPassword", encodedPassword);
strength参数控制迭代次数,值越大加密越慢,安全性越高。通常设10或12是平衡安全与性能的合理选择。
安全调优做在前面,永远比事后修补漏洞成本低得多。把基础防线夯实,系统上线后才能稳如磐石。
