StringEscapeUtils防止sql注入

本文介绍了一种使用org.apache.commons.lang.StringEscapeUtils.escapeSql方法来转义字符串中的特殊字符,从而有效防止SQL注入攻击的安全措施。

调用org.apache.commons.lang.StringEscapeUtils.escapeSql(String str)方法可以将str里边的特殊字符转换,避免sql注入

<think>我们正在讨论Java中防止SQL注入的最佳实践,特别是针对字符串拼接问题。根据引用内容,参数化查询(PreparedStatement)是首选方案。此外,引用还提到MyBatis中#{}和${}的区别,以及druid过滤的局限性。 核心解决方案: 1. 强制使用PreparedStatement(参数化查询)处理所有用户输入的数据。 2. 在MyBatis等ORM框架中,优先使用#{}(预编译)而非${}(字符串拼接)。 3. 如果必须使用字符串拼接(如动态表名、列名),则必须严格白名单验证。 具体步骤: ### 1. 使用PreparedStatement(JDBC原生方式) 这是Java防止SQL注入最根本的方法。原理是将SQL语句结构和数据分离,数据库引擎不会将数据视为指令部分。 ```java String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setString(1, username); // 自动转义,防止注入 stmt.setString(2, password); try (ResultSet rs = stmt.executeQuery()) { // 处理结果集 } } ``` ### 2. 在MyBatis中使用#{}预编译 MyBatis中,#{}会将参数替换为?,然后使用PreparedStatement执行,能有效防止注入。而${}是直接拼接字符串,存在注入风险。 安全方式: ```xml <select id="findUser" resultType="User"> SELECT * FROM users WHERE username = #{username} </select> ``` 危险方式(避免使用): ```xml <select id="findUser" resultType="User"> SELECT * FROM users WHERE username = '${username}' <!-- 存在注入风险 --> </select> ``` ### 3. 动态表名/列名处理(必须拼接时) 当需要动态指定表名或列名(如排序字段)时,无法使用参数化查询。此时必须: - 使用白名单验证:只允许预定义的、安全的表名或列名。 - 避免直接使用用户输入。 示例(动态排序): ```java // 定义允许的排序字段白名单 private static final Set<String> ALLOWED_SORT_COLUMNS = Set.of("id", "username", "email"); public List<User> getUsers(String sortField) { // 检查用户传入的排序字段是否在白名单中 if (!ALLOWED_SORT_COLUMNS.contains(sortField)) { sortField = "id"; // 默认值 } String sql = "SELECT * FROM users ORDER BY " + sortField; // 此时拼接安全 // ...执行查询 } ``` ### 4. 避免使用字符串拼接生成SQL语句 绝对不要这样写: ```java String sql = "SELECT * FROM users WHERE username = '" + username + "'"; // 高危! ``` ### 5. 辅助安全措施 - **使用Druid等连接池的SQL防火墙**:配置`wallFilter`,但注意它不能完全依赖(如引用[4]所述,`${}`传递`1 or state=2`可能绕过)。 - **最小权限原则**:数据库账号只赋予必要权限。 - **输入验证**:对用户输入进行格式、长度等校验(如邮箱格式)。 ### 总结最佳实践 1. **首选参数化查询**(PreparedStatement或MyBatis的#{})处理所有用户输入的数据。 2. **动态表名/列名**采用**白名单验证**,确保拼接内容安全。 3. **避免任何形式的字符串拼接SQL**,特别是将用户输入直接拼接到SQL中。 4. 使用ORM框架(如Hibernate)时,同样遵循其安全查询方式(如HQL参数绑定)。 > 引用说明: > 参数化查询通过分离指令与数据从根本上防止注入[^1]。MyBatis中#{}预编译机制能有效防止SQL注入[^2]。而使用${}或字符串拼接则存在风险,即使使用Druid过滤也可能被绕过[^4]。 §§相关问题§§ 1. MyBatis中#{}和${}的区别是什么?为什么#{}能防止SQL注入而${}不能? 2. 在Java中,除了PreparedStatement,还有哪些防止SQL注入的库或框架? 3. 如何安全地处理SQL语句中的IN条件(如WHERE id IN (1,2,3))以防止注入? 4. 使用白名单验证动态表名时,如何设计白名单才能确保安全性?</think>### Java中防止String拼接SQL注入的最佳实践 在Java中防止SQL注入的核心原则是**避免直接拼接用户输入到SQL语句**。以下是针对String拼接问题的具体解决方案: --- #### ✅ 1. **强制使用PreparedStatement(首选方案)** **原理**:将SQL语句结构和数据分离,数据库引擎自动处理特殊字符转义。 ```java // 安全示例 String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; try (PreparedStatement stmt = connection.prepareStatement(sql)) { stmt.setString(1, userInput1); // 自动转义 stmt.setString(2, userInput2); // 自动转义 ResultSet rs = stmt.executeQuery(); } ``` **优势**: - 自动处理所有特殊字符(如 `'`, `;`, `--`) - 明确区分SQL结构和数据[^1][^2] - 支持类型安全验证(如`setInt()`防止非数字输入) --- #### ✅ 2. **在MyBatis中优先使用`#{}`语法** **原理**:`#{}`会生成预编译的PreparedStatement,而`${}`是直接拼接字符串。 ```xml <!-- 安全方式 --> <select id="findUser" resultType="User"> SELECT * FROM users WHERE username = #{username} <!-- 预编译 --> </select> <!-- 危险方式(避免使用) --> <select id="findUser" resultType="User"> SELECT * FROM users WHERE username = '${username}' <!-- 直接拼接 --> </select> ``` **关键点**: - `#{}` 等效于JDBC的`?`占位符[^2] - `${}` 仅用于**动态表名/列名**等非用户输入场景 --- #### ⚠️ 3. **动态表名/列名的安全处理** 当必须拼接表名或排序字段时: ```java // 步骤1:定义白名单 private static final Set<String> SAFE_COLUMNS = Set.of("id", "name", "email"); // 步骤2:验证用户输入 public String safeOrderBy(String userInput) { if (!SAFE_COLUMNS.contains(userInput)) { throw new IllegalArgumentException("Invalid column name"); } return userInput; // 返回安全值 } // 使用示例 String sql = "SELECT * FROM users ORDER BY " + safeOrderBy(input); ``` --- #### 🛡️ 4. **多层防御策略** 1. **输入验证**(前置过滤): ```java // 白名单验证示例 if (!userInput.matches("[a-zA-Z0-9_]+")) { // 只允许字母数字下划线 throw new InvalidInputException(); } ``` 2. **使用安全工具库**: ```java // Apache Commons Lang3 String safeInput = StringEscapeUtils.escapeSql4(userInput); // 注意:这只是辅助手段,不能替代参数化查询! ``` 3. **连接池防护**(如Druid): ```java // 在配置中启用SQL防火墙 datasource.setFilters("wall"); ``` **局限**:如引用[4]所述,无法完全防御`state=${state}`类注入[^4] --- #### ❌ 绝对禁止的做法 ```java // 高危!直接拼接用户输入 String sql = "SELECT * FROM users WHERE name = '" + userInput + "'"; Statement stmt = connection.createStatement(); stmt.executeQuery(sql); // 100%可被注入 ``` --- ### 总结最佳实践 1. **首选方案**:对所有用户输入使用`PreparedStatement`或MyBatis的`#{}`[^1][^2] 2. **动态标识符**:用**白名单验证**处理表名/列名拼接 3. **纵深防御**:结合输入验证 + 预编译 + 最小数据库权限 4. **避免**:`${}`、字符串拼接、单纯依赖转义工具或连接池过滤 > 通过参数化查询,即使用户输入`' OR '1'='1`,数据库也会将其视为普通字符串而非SQL指令[^3],从根本上阻断注入。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值