SQL 注入风险与解决方案实战解析
在日常开发中,数据库操作几乎是绕不开的环节。很多同学写查询语句的时候,习惯直接用字符串拼接,比如:
大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
前言
在日常开发中,数据库操作几乎是绕不开的环节。很多同学写查询语句的时候,习惯直接用字符串拼接,比如:
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
乍一看挺正常,但一旦碰上心怀不轨的攻击者,后果就可能非常严重。这就是我们常说的 SQL 注入 问题。
什么是 SQL 注入
SQL 注入的本质就是:
用户的输入被直接拼接到 SQL 语句中,没有做任何防护,导致数据库把攻击者的输入当成真正的 SQL 指令去执行。
举个最经典的例子:
如果登录接口这样写:
String username = "admin";
String password = "' OR '1'='1"; // 攻击者输入的内容
String sql = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'";
System.out.println(sql);
拼接后的 SQL 就变成了:
SELECT * FROM users WHERE username='admin' AND password='' OR '1'='1'
这一句 OR '1'='1'
永远成立,所以攻击者轻轻松松绕过了密码验证,直接登录成功。
真实场景下的危害
别以为这只是理论,现实中因为 SQL 注入出问题的案例太多了。常见危害包括:
- 绕过身份认证:像上面的例子,用户不用知道密码也能登录。
- 数据泄露:攻击者可以拼接语句,把整个表的数据都查询出来。
- 数据篡改或删除:严重的情况下,甚至可以执行
DROP TABLE users;
直接把表删掉。 - 权限提升:通过复杂的注入方式,攻击者可能拿到数据库更高权限,导致系统全面失控。
所以在企业开发里,SQL 注入算是基础中的基础,必须提前预防。
常见解决方案
那我们该怎么防范呢?有几种常见的方式:
1. 使用 PreparedStatement
PreparedStatement 是 JDBC 提供的预编译语句对象。它会先把 SQL 模板交给数据库编译好,然后再传入参数。这样参数和 SQL 逻辑严格分离,用户输入就不会被当成 SQL 指令执行。
Demo 代码:
import java.sql.*;
public class SafeLoginDemo {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/testdb";
String user = "root";
String pass = "123456";
String inputUser = "admin";
String inputPass = "' OR '1'='1";
Connection conn = DriverManager.getConnection(url, user, pass);
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, inputUser);
stmt.setString(2, inputPass);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
System.out.println("登录成功: " + rs.getString("username"));
} else {
System.out.println("用户名或密码错误");
}
rs.close();
stmt.close();
conn.close();
}
}
这里无论用户输入什么奇怪的密码,数据库都会把它当成一个普通的字符串参数处理,而不是 SQL 逻辑。
2. MyBatis 使用 #{}
占位符
在 MyBatis 中,有两个写法经常被混淆:${}
和 #{}
。
${}
:直接拼接字符串,可能导致 SQL 注入。#{}
:安全的参数绑定,底层会帮你用 PreparedStatement。
错误写法(容易被注入):
<select id="getUserByName" resultType="User">
SELECT * FROM users WHERE username = '${username}'
</select>
正确写法(推荐使用):
<select id="getUserByName" resultType="User">
SELECT * FROM users WHERE username = #{username}
</select>
这样就算有人传了 ' OR '1'='1
这样的输入,也只会作为字符串参数传入,不会破坏 SQL 逻辑。
3. 使用 ORM 框架封装查询
像 Hibernate、JPA 这类 ORM 框架,通常都已经封装了参数绑定逻辑,默认情况下就能避免注入风险。
比如用 Spring Data JPA:
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsernameAndPassword(String username, String password);
}
Spring Data JPA 底层会自动生成类似 PreparedStatement 的语句,所以基本不需要担心 SQL 注入问题。
总结
回过头来看,SQL 注入的核心原因是 “把用户输入当成 SQL 语句的一部分”。
解决的思路就是 “参数化查询”,让 SQL 和用户输入严格分离。
落地建议:
- 在 JDBC 层,统一用
PreparedStatement
。 - 在 MyBatis 中,统一用
#{}
而不是${}
。 - 在使用 ORM 框架时,不要为了图省事拼接 JPQL/HQL。
- 养成安全开发习惯,做代码审查时重点关注数据库查询部分。
更多推荐
所有评论(0)