不安全的拼接方式(严禁使用)
示例:字符串直接拼接
String userId = "123"; // 假设来自用户输入 String sql = "SELECT * FROM users WHERE id = " + userId;
风险:若用户输入为123 OR 1=1
,会泄露全表数据。永远不要这样写!
安全方法:使用预编译语句(PreparedStatement)
这是Java核心库提供的防SQL注入方案,强烈推荐。
String sql = "SELECT * FROM users WHERE id = ? AND name = ?"; try (Connection conn = DriverManager.getConnection(url, user, pass); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setInt(1, 123); // 设置第一个参数(索引从1开始) pstmt.setString(2, "John"); // 设置第二个参数 ResultSet rs = pstmt.executeQuery(); // 处理结果集... }
优势:
- 自动转义特殊字符(如 →
'
)。 - 防止SQL注入攻击。
- 提升性能(SQL模板可复用)。
动态SQL拼接场景
当条件数量不确定时(如搜索过滤器),需安全拼接:
使用StringBuilder + PreparedStatement
List<String> conditions = new ArrayList<>(); List<Object> params = new ArrayList<>(); StringBuilder sql = new StringBuilder("SELECT * FROM products WHERE 1=1"); if (priceMin != null) { sql.append(" AND price >= ?"); params.add(priceMin); } if (category != null) { sql.append(" AND category = ?"); params.add(category); } try (PreparedStatement pstmt = conn.prepareStatement(sql.toString())) { for (int i = 0; i < params.size(); i++) { pstmt.setObject(i + 1, params.get(i)); } // 执行查询... }
使用第三方库(推荐)
- Apache Commons DbUtils:简化参数设置。
- Spring JdbcTemplate:
String sql = "SELECT * FROM products WHERE name = ? AND status = ?"; List<Product> products = jdbcTemplate.query( sql, new Object[]{"Laptop", "active"}, new BeanPropertyRowMapper<>(Product.class) );
ORM框架:更高级的解决方案
JPA/Hibernate(HQL)
// 自动生成安全SQL String hql = "FROM User WHERE email = :email"; Query<User> query = session.createQuery(hql, User.class); query.setParameter("email", "user@example.com"); List<User> users = query.list();
MyBatis(动态SQL标签)
<!-- XML映射文件 --> <select id="findUsers" resultType="User"> SELECT * FROM users <where> <if test="name != null">AND name = #{name}</if> <if test="role != null">AND role = #{role}</if> </where> </select>
// Java调用 Map<String, Object> params = new HashMap<>(); params.put("role", "admin"); List<User> users = sqlSession.selectList("findUsers", params);
特殊场景注意事项
-
表名/列名动态拼接:
- 禁止直接拼接用户输入,应使用白名单校验:
Set<String> validColumns = Set.of("name", "email", "age"); String column = "email"; if (!validColumns.contains(column)) throw new IllegalArgumentException(); String sql = "SELECT " + column + " FROM users"; // 谨慎使用
- 禁止直接拼接用户输入,应使用白名单校验:
-
IN语句处理:
- 使用
PreparedStatement
的批处理:String sql = "SELECT * FROM items WHERE id IN (?, ?, ?)"; pstmt.setInt(1, 101); pstmt.setInt(2, 102); pstmt.setInt(3, 103);
- MyBatis提供
<foreach>
标签:<select id="getByIds"> SELECT * FROM items WHERE id IN <foreach item="id" collection="ids" open="(" separator="," close=")"> #{id} </foreach> </select>
- 使用
最佳实践
- 首选方案:
- 静态SQL →
PreparedStatement
- 动态SQL →
PreparedStatement
+ 条件拼接(或ORM框架)
- 静态SQL →
- 严格避免:
- 字符串拼接(、
String.format()
)、非转义用户输入。
- 字符串拼接(、
- 性能优化:
对高频SQL启用缓存(如Hibernate二级缓存)。
- 安全原则:
- 最小权限原则(数据库账号限制权限)。
- 输入验证(长度、类型、格式)。
引用说明:
- OWASP SQL注入防护指南:https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
- Oracle官方PreparedStatement文档:https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
- MyBatis动态SQL文档:https://mybatis.org/mybatis-3/dynamic-sql.html 遵循E-A-T原则(专业性、权威性、可信度),基于Java官方规范及行业安全标准编写。*
原创文章,发布者:酷盾叔,转转请注明出处:https://www.kd.cn/ask/40883.html