本文目录导读:

在Java中,使用预编译SQL(PreparedStatement)是防止SQL注入最有效的方法,下面是完整的防注入案例和原理说明:
核心原理
预编译SQL的防注入原理:参数与SQL语句分离编译,参数值被当作纯数据处理,不会改变SQL语义。
正确案例(防注入)
1 基本PreparedStatement使用
public class SafeQueryExample {
// 正确:使用PreparedStatement
public User getUserById(Long id) {
String sql = "SELECT * FROM users WHERE id = ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 参数绑定(自动处理转义)
pstmt.setLong(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return mapUser(rs);
}
}
} catch (SQLException e) {
log.error("查询用户失败", e);
}
return null;
}
// 多个参数示例
public List<User> searchUsers(String username, String email) {
String sql = "SELECT * FROM users WHERE username = ? AND email = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, username);
pstmt.setString(2, email);
try (ResultSet rs = pstmt.executeQuery()) {
List<User> users = new ArrayList<>();
while (rs.next()) {
users.add(mapUser(rs));
}
return users;
}
}
}
}
2 MyBatis框架示例
<!-- Mapper XML文件:使用#{}实现预编译 -->
<select id="getUser" resultType="User">
SELECT * FROM users
WHERE username = #{username}
AND password = #{password}
<!-- #{xxx} 会生成 PreparedStatement 的 ? 占位符 -->
</select>
// MyBatis Mapper接口
@Mapper
public interface UserMapper {
// 安全:使用#{}
@Select("SELECT * FROM users WHERE username = #{username}")
User findByUsername(@Param("username") String username);
// 安全:动态SQL也使用#{}
@Select("<script>" +
"SELECT * FROM users WHERE 1=1" +
"<if test='username != null'> AND username = #{username}</if>" +
"<if test='email != null'> AND email = #{email}</if>" +
"</script>")
List<User> findByCondition(UserQuery query);
}
3 JPA/Hibernate示例
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 安全:JPQL使用参数绑定
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsername(@Param("username") String username);
// 安全:Native SQL也支持参数绑定
@Query(value = "SELECT * FROM users WHERE username = :username",
nativeQuery = true)
User findByUsernameNative(@Param("username") String username);
}
// EntityManager方式
public User findUserByCondition(String username) {
String jpql = "SELECT u FROM User u WHERE u.username = :username";
return entityManager.createQuery(jpql, User.class)
.setParameter("username", username)
.getSingleResult();
}
错误案例(易注入)
// ❌ 错误1:使用字符串拼接SQL
public User findUserByUsername(String username) {
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
// 如果username = "' OR '1'='1",将查询所有用户!
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
}
// ❌ 错误2:MyBatis中使用${}
@Select("SELECT * FROM users WHERE username = ${username}")
// 使用${}不会预编译,会直接替换,存在注入风险
User findUserUnsafe(@Param("username") String username);
// ❌ 错误3:JPA中原生SQL拼接
@Query(value = "SELECT * FROM users WHERE username = '" + ":username" + "'",
nativeQuery = true)
// 错误的参数绑定方式
特殊场景处理
1 LIKE查询防注入
// 正确:手动处理通配符转义
public List<User> searchUsers(String keyword) {
String sql = "SELECT * FROM users WHERE username LIKE ? ESCAPE '/'";
// 转义特殊字符
String safeKeyword = keyword
.replace("!", "!!")
.replace("%", "!%")
.replace("_", "!_")
.replace("[", "![");
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, "%" + safeKeyword + "%");
// ...
}
}
2 IN子句处理
// 正确:动态生成占位符
public List<User> findUsersByIds(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
// 生成占位符: ?, ?, ?
String placeholders = ids.stream()
.map(id -> "?")
.collect(Collectors.joining(", "));
String sql = "SELECT * FROM users WHERE id IN (" + placeholders + ")";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
for (int i = 0; i < ids.size(); i++) {
pstmt.setLong(i + 1, ids.get(i));
}
// ...
}
}
// MyBatis方式:使用collection
@Select("<script>" +
"SELECT * FROM users WHERE id IN " +
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
List<User> findByIds(@Param("ids") List<Long> ids);
3 动态排序(特殊处理)
// 排序字段不能使用预编译,需要白名单校验
public List<User> findUsers(String sortField, String sortOrder) {
// 白名单校验
Set<String> allowedFields = new HashSet<>(Arrays.asList(
"id", "username", "email", "create_time"
));
if (!allowedFields.contains(sortField)) {
throw new IllegalArgumentException("Invalid sort field");
}
// 校验排序方向
String order = "ASC".equalsIgnoreCase(sortOrder) ? "ASC" : "DESC";
String sql = "SELECT * FROM users ORDER BY " + sortField + " " + order;
// 这里字段名经过白名单校验,相对安全
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery(sql);
// ...
}
}
最佳实践总结
1 原则
- 永远不要拼接SQL字符串,除非经过严格白名单校验
- 始终使用预编译API:PreparedStatement, #{}, :param
- 输入验证:在业务层进行参数合法性校验
- 最小权限:数据库用户只赋予必要的权限
2 检查清单
| 场景 | 正确做法 | 错误做法 |
|---|---|---|
| 基本查询 | WHERE id = ? |
WHERE id = '${id}' |
| MyBatis | ||
| LIKE查询 | 手动转义通配符 | 直接拼接 |
| 排序字段 | 白名单校验 | 直接拼接 |
| IN子句 | 动态?占位符/foreach | 字符串拼接 |
| 批量操作 | batch + 参数绑定 | 循环执行Statement |
防护验证示例
// 注入攻击测试
public void testInjection() {
String maliciousInput = "' OR '1'='1";
// 使用PreparedStatement
String safeSql = "SELECT * FROM users WHERE username = ?";
try (PreparedStatement pstmt = conn.prepareStatement(safeSql)) {
pstmt.setString(1, maliciousInput); // 当作普通字符串处理
ResultSet rs = pstmt.executeQuery();
// 不会返回所有用户,因为条件变成了:
// username = '' OR '1'='1' => 被转义为字符串
// 实际执行的SQL:SELECT * FROM users WHERE username = ''' OR ''1''=''1'
}
// 使用Statement拼接
String unsafeSql = "SELECT * FROM users WHERE username = '" + maliciousInput + "'";
// 实际执行的SQL:SELECT * FROM users WHERE username = '' OR '1'='1'
// 返回所有用户!存在注入漏洞
}
通过严格遵守这些规范,可以有效防止SQL注入攻击,保护数据安全。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播