你真的会node操作数据库吗?考虑过sql注入吗

1,570 阅读5分钟

插件安装

如果你使用的是vscode的话,我们都知道,里面和idea不一样,并没有自带的数据库连接管理,所以我们需要一个类似于idea的数据库管理。 在vs里我个人用着感觉还不错的两个插件

image.png

image.png 大家可以自行了解一下这两款工具,目前database client是有收费和免费的,免费的最多建立三个连接,但是个人感觉,比shell做的要体贴一点,各取所需吧。当然桌面级应用也是可以的。

image.png

数据库连接

与java一样,node并不能直接连接数据库,也需要类似jdbc这样的中间层的存在,在node中,我们推荐使用mySql2这个第三方库来辅助我们。下面是具体地址 sidorares.github.io/node-mysql2…

npm install --save mysql2   进行安装

具体配置

mysql2使用起来也非常方便,具体配置如下

const mysql = require("mysql2");

const connection = mysql.createConnection({
  host: "localhost", // 数据库地址
  user: "root", // 数据库用户名
  password: "password", // 数据库密码
  database: "mydatabase", // 要连接的数据库名
  charset: "UTF8_GENERAL_CI", // 连接字符集,默认为 UTF8_GENERAL_CI
  connectTimeout: 10000, // 连接超时时间,单位为毫秒
  multipleStatements: false, // 是否允许一个 query 中有多个 MySQL 语句,默认为 false
});

此时我们就创立好的连接,我们可以测试一下,一个简单的sql语句,query函数传入两个参数,第一个是sql语句,第二个是回调函数,当然还有第三个,感兴趣的可以自己去了解一下

connection.query("select * from employee;", function (err, result) {
  console.log(result);
});

这是数据库中的数据,控制台是执行程序后输出的结果 image.png

但是但是,并不止如此,我们不妨看一下,result是什么

connection.query(
  "update employee SET name = '张云6' WHERE id = 1",
  function (err, result) {
    console.log(result);
  }
);
connection.end();//关闭数据库连接

image.png 我们以此来看一下这是什么

在 Node.js 中使用 mysql2 或其他 MySQL 客户端库执行数据库操作时, ResultSetHeader 对象的输出。这个对象提供了关于执行 SQL 语句后数据库状态的一些详细信息。

fieldCount:表示返回的字段数量。在 INSERT、UPDATE 或 DELETE 操作中,这个值通常是 0,因为这些操作不返回数据行。

affectedRows:表示操作影响的行数。例如,在一个更新语句中,这可能是指被更新的行数。

insertId:对于 INSERT 操作,这个字段表示插入操作生成的 AUTO_INCREMENT 值。如果插入操作没有生成 AUTO_INCREMENT 值,则这个字段为 0。

info:一个字符串,提供了关于操作的其他信息。例如,它可能包含匹配的行数、改变的行数和警告的数量。

serverStatus:一个数值,表示服务器状态。这个值通常用于诊断或检查服务器状态。

warningStatus:表示操作产生的警告数量。警告通常不是错误,但它们可能指示一些需要注意的问题。

changedRows:对于 UPDATE 操作,这个字段表示实际改变的行数(即,至少有一个字段被修改的行数)。

至此,我们已经完成了一个简单的数据库操作了 下面是一些进阶一点的方法,除了使用const mysql = require("mysql2");引入之外,mysql2还提供了promise的方式,具体的看下面的配置

const mysql = require("mysql2/promise");

async function test2() {
  const connection = await mysql.createConnection({
  host: "localhost", // 数据库地址
  user: "root", // 数据库用户名
  password: "password", // 数据库密码
  database: "mydatabase", // 要连接的数据库名
  charset: "UTF8_GENERAL_CI", // 连接字符集,默认为 UTF8_GENERAL_CI
  connectTimeout: 10000, // 连接超时时间,单位为毫秒
  multipleStatements: false, // 是否允许一个 query 中有多个 MySQL 语句,默认为 false
  });
  const [result] = await connection.query("select * from employee;");
  console.log(result);
  connection.end();
}
test2();

image.png 依旧可以成功,但是因为使用了promise 的方式,在某些层面上会降低我们开发的复杂度,但是,我们可以发现,创建数据库的连接在函数中,难免我们会遇到函数多次执行的问题,如果我们没有即使将数据库的连接关闭掉,可能会引发更严重的问题 所以,我们引入数据池的概念

使用数据池

使用数据池,我们需要使用的不再是createConnection,而是mysql.createPool 具体配置如下

const mysql = require("mysql2/promise");
//使用连接池
const pool = mysql.createPool({
  //建立连接池
  host: "localhost",
  user: "root",
  password: "dizao06",
  database: "test",
  waitForConnections: true,
  multipleStatements: true, //允许运行多条sql
});

async function test() {
  const [result] = await pool.query("select * from employee;");
  console.log(result);
  pool.end();
}
test();

image.png 这样我们就无需每次运行函数都创建一个数据库连接了

sql注入

思考这样一个问题,我要查询指定id的信息

很容易我们就想到,select * from employee where id=? 这样的语句,事实也确实如此 我们来写一下

const mysql = require("mysql2/promise");
//使用连接池
const pool = mysql.createPool({
  //建立连接池
  host: "localhost",
  user: "root",
  password: "dizao06",
  database: "test",
  waitForConnections: true,
  multipleStatements: true, //允许运行多条sql
});
async function test(id) {
  const sql = `select * from employee where id=${id}`;
  const [result] = await pool.query(sql);
  console.log(result);
  pool.end();
}
test(1);

结果也如我们所料,确实可以查询出来结果

image.png 但是我们考虑一个问题,这玩意最后不都是要被写成接口的吗,如果说用户来执行这个操作呢?用户带来的消息可信吗?很显然不可信,比如说,用户在控制台不知道用什么方法执行了你这个函数,正常执行当然可以,可是假如他这样执行呢,以这个数据为例

image.png

async function test(id) {
  console.log(id);
  const sql = `select * from employee where id=${id}`;
  const [result] = await pool.query(sql);
  console.log(result);
  pool.end();
}
test("'';DELETE FROM company WHERE id = '4';");

你猜会如何呢?我传入的id是'';DELETE FROM company WHERE id = '4';的字符串,又通过模板字符串拼接了上去

image.png

image.png

数据已经被删掉了,仔细一想这不是一件很可怕的事情吗,你要查询指定的人,却删掉了整个数据库的文件,这是一件很怕的事情

为了解决sql注入,我们可以采用这样的方式,我们不再使用query去执行sql,而转而使用execute,在这里使用execute,我们可以将sql写成如下的格式

const sql = "select * from employee where id=?",

将需要传递参数的地方用?来代替,接着在执行的时候pool.execute(sql, [id]),

传入的第二个参数是一个数组,他会按照顺序以此放入?的位置

async function test(id) {
  const sql = "select * from employee where id=?";
  const [result] = await pool.execute(sql, [id]); //防止sql注入
  console.log(result);
  pool.end();
}
test("'';DELETE FROM company WHERE id = '3';");

最后查询为[]
async function test(id, id2) {
  const sql = "select * from employee where id=? or id=?";
  const [result1] = await pool.execute(sql, [id, id2]); //防止sql注入
  console.log(result1);
  pool.end();
}
test("'';DELETE FROM company WHERE id = '3';", "3");

image.png

当然,这么写还是很麻烦很麻烦,当然存在其他方法比如orm框架,就不再这里介绍了

ORM原理图.jpg