JDBC编程——SQL注入问题以及解决方案

本文探讨了SQL注入的安全隐患及其对数据库系统的威胁,详细解释了其根本原因在于用户输入未经验证参与SQL语句编译。为解决此问题,文章推荐使用PreparedStatement,介绍了其预编译机制和安全性。通过对比Statement与PreparedStatement,强调了后者在防止SQL注入和提高效率方面的优势,并给出了使用PreparedStatement的示例代码。同时,也指出在特定业务需求下,如需支持SQL语句拼接时,仍需使用Statement。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、在JDBC 连接数据库方面存在一个问题:SQL注入(安全隐患)

SQL注入即是指应用程序对用户输入数据的合法性没有判断或过滤不严,一些非法攻击者可以在应用程序中事先定义好的查询语句的结尾上添加额外的,导致在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。

比如在我们用户登录 的时候,使用到了以下的登录密码:

/*用户名:sa
密码:ds' or '1'='1
登录成功*/
//select * from t_user where loginname='sa'and loginpw='ds' or '1'='1'
这里的密码使用到了SQL语句关键字:or,数据库服务器把它当作了sql语句来执行,导致用户登陆成功。这种现象被称为sql注入(黑客常用)
  • SQl注入安全隐患的根本原因是:

    • 用户输入的信息中含有sql语句的关键字并且这些关键字参与sql语句的编译过程导致sql语句的意愿被扭曲,进而达到sql注入。

二、解决SQL注入问题的方案:

只要用户提供的信息不参与sql语句的编译过程,问题就解决了。   即使用户提供的信息中含有sql语句的关键字,但是没有参与编译,仍然不起作用 。

要想用户信息不参与SQL语句的编译,那么必须要使用java.sql.PreparedStatement

PreparedStatement接口继承了java.sql.Statement

PreparedStatement是属于预编译的数据库对象

PreparedStatement的原理是:预先对SQL语句的框架进行编译。然后再给SQL语句传“值”

import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/*解决SQL注入问题:
* 测试结果:
*       用户名:sa
        密码:ds' or '1'='1
        登录失败
    */
public class JDBCtest {
    public static void main(String[] args) {
        //初始化一个界面
        Map<String, String> userloginInfo = initUI();
        //验证用户名和密码
        Boolean loginSuccess = login(userloginInfo);
        System.out.println(loginSuccess?"登录成功":"登录失败");
    }
    /**
     * 用户登录
     * @param userLogin 用户登录信息
     * @return false表示失败,true表示成功,
     */
    private static Boolean login(Map<String, String> userLogin) {
        //标识变量
        Boolean flag =false;
        //JDBC代码
        ResultSet rs = null;
        PreparedStatement ps = null;
        Connection conn = null;
        //单独定义变量
        String loginname = userLogin.get("loginname");//获取用户输入的Map集合里面的“key”
        String loginpw = userLogin.get("loginpw");
        try {
            //1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/sql", "root", "123456");
            //3获取预编译的数据库操作对象
//SQL语句的框架这,两个?我们称为”占位符“,一个?表示一个占位符,一个?只能接受一个值,注意:?不能用单引号括起来。
            String dml = "select * from t_user where loginname=? and loginpw=?";
            //程序执行到此处,会发送SQL语句框架给DBMS,然后DBMS会进行对SQL语句的预编译
            ps = conn.prepareStatement(dml);
            //编译完成之后,就要对SQL语句“占位符?”进行传值(只编译一次)
            //(第一个?下标是1,第二个?下标是2,JDBC中所有的下标都是从1开始的.
            ps.setString(1,loginname);
            ps.setString(2,loginpw);
            //4、执行sql
            rs = ps.executeQuery();
            //5处理结果集
            if (rs.next()){
                flag= true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                try {
                    if (ps!= null) {
                        ps.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                try {
                    if (conn != null) {
                        conn.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            return flag;
        }
    }
        /**
         * 初始化用户界面
         * @return 输入用户名和密码等信息;
         */
        private static Map<String, String> initUI () {
            Scanner s = new Scanner(System.in);
            System.out.print("用户名:");
            String loginname = s.nextLine();
            System.out.print("密码:");
            String loginpw = s.nextLine();
            //把用户名和密码组装到Map集合中去:
            Map<String, String> userloginMap = new HashMap<>();
            userloginMap.put("loginname", loginname);
            userloginMap.put("loginpw", loginpw);
            return userloginMap;
        }
}

三、Statement和preparedStatement的对比

  • Statement存在SQl注入问题,preparedStatement解决了该问题

  • Statement是编译一次执行一次,preparedStatement是编译一次可执行N次,preparedStatement效率较高

  • preparedStatement会在编译阶段做类型的安全检查。

综上所述,preparedStatement使用较多,Statement极少数的情况下才使用。

2、那么什么情况下必须使用Statement呢?

  • 就是业务方面必须要求支持sql注入的时候。,进行SQl语句拼接的,必须使用Statement。

  • 例如:以下的程序给表中的数据排序,要用到SQL语句的拼接。

import java.sql.*;
import java.util.Scanner;
public class JDBCtest {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        System.out.println("输入desc或asc,desc表示降序,asc表示升序:");
        String d = s.nextLine();
//JDBC代码
        ResultSet rs = null;
        Statement statement = null;
        Connection conn = null;
        try {
            //1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/sql", "root", "123456");
            //3获取数据库操作对象
            statement = conn.createStatement();
            //4、执行sql
            String dml = "select username  from t_student order by username "+d;
            rs = statement.executeQuery(dml);
            //5处理结果集
            while (rs.next()) {
                System.out.println(rs.getString("username"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //6释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                try {
                    if (statement != null) {
                        statement.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                try {
                    if (conn != null) {
                        conn.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
输出结果:
/*输入desc或asc,desc表示降序,asc表示升序:
  asc
        jack
        ls
        rose
        zs
*/

好了,以上就是我对SQL注入问题的解决方案,与大家一起共享,希望对大家可能有一定的帮助

你的问题得到解决了吗?欢迎在评论区留言,提出更好的意见和建议 。  

### Java 嵆入式 SQL 数据库编程概述 Java 中的嵌入式 SQL 数据库编程涉及将 SQL 查询集成到 Java 应用程序中,从而实现对数据库的操作。这种技术通常用于简化数据管理并提高应用程序性能。以下是有关如何使用 Java 进行嵌入式 SQL 数据库编程的一些核心概念和技术细节。 #### 使用 JDBC 访问嵌入式数据库 JDBCJava Database Connectivity)是一种标准 API,允许 Java 程序各种类型的数据库交互。对于嵌入式数据库,可以通过 JDBC 加载相应的驱动程序来建立连接[^2]。例如: ```java // 导入必要的包 import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; public class EmbeddedDatabaseExample { public static void main(String[] args) { try { // 注册 SQLite 驱动程序 Class.forName("org.sqlite.JDBC"); // 创建数据库连接 String url = "jdbc:sqlite:test.db"; // test.db 是本地文件名 Connection connection = DriverManager.getConnection(url); // 执行 SQL 语句 Statement statement = connection.createStatement(); String sql = "CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, name TEXT)"; statement.execute(sql); System.out.println("Table created successfully."); // 关闭资源 statement.close(); connection.close(); } catch (Exception e) { e.printStackTrace(); } } } ``` 此代码片段展示了如何通过 JDBC 使用 SQLite 嵌入式数据库创建一个简单的表结构[^3]。 --- #### 嵌入式数据库的选择 在 Java 开发中常用的嵌入式数据库包括 SQLite 和 UnQLite。这些数据库具有轻量级、无需单独服务器进程的特点,非常适合单机环境下的应用开发。 - **SQLite**: 提供了完整的 SQL 支持,并且由于其实现简单高效而广受欢迎。它可以轻松处理中小型项目的数据存储需求[^3]。 - **UnQLite**: 如果需要更灵活的设计模式,则可以选择 NoSQL 类型的嵌入式解决方案——如 UnQLite。它不仅支持键值对存储模型,还可以作为文档数据库使用[^4]。 --- #### 动态 SQL 的优势 相比于静态编写的嵌入式 SQL,动态 SQL 更加灵活,因为它可以根据运行时条件构建查询字符串。下面是一个基于 PreparedStatement 的例子,演示了如何安全地传递参数给 SQL 查询以防止注入攻击: ```java import java.sql.PreparedStatement; import java.sql.ResultSet; public class DynamicSqlExample { public static void queryUser(Connection conn, String username) throws Exception { String sql = "SELECT * FROM users WHERE name=?"; try (PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, username); // 设置第一个占位符 ResultSet rs = pstmt.executeQuery(); while(rs.next()){ int userId = rs.getInt("id"); String userName = rs.getString("name"); System.out.printf("ID=%d Name=%s%n", userId, userName); } } } } ``` 这种方法利用 `?` 占位符代替硬编码变量值的方式显著提高了安全性[^1]。 --- #### Vert.x MongoDB 客户端简介 尽管严格意义上讲 MongoDB 不属于传统意义上的关系型嵌入式数据库范畴,但在现代微服务架构下,有时也会将其视为一种特殊的“嵌入式”形式加以运用。Vert.x 提供了一套异步非阻塞风格的工具集来操作此类分布式持久化引擎[^5]。以下是从官方文档摘录的一个保存记录的例子: ```java MongoClient mongoClient = MongoClient.createNonShared(vertx, config); JsonObject document = new JsonObject() .put("title", "The Hobbit") .put("author", "J.R.R. Tolkien"); mongoClient.save("books", document, res -> { if (res.succeeded()) { System.out.println("Saved book with id " + res.result()); } else { res.cause().printStackTrace(); } }); ``` 这段脚本说明了即使是在高度并发场景里也能保持良好表现力的同时完成基本 CRUD 操作。 --- ### 总结 以上介绍了几种常见的 Java 嵌入式 SQL 数据库编程实践及其应用场景。无论是传统的 RDBMS 如 SQLite,还是新兴的 NoSQL 解决方案像 UnQLite 或者远程托管的服务实例例如 MongoDB,开发者都可以借助于成熟的框架快速搭建起稳定可靠的后台支撑体系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-今非昔比°

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值