我们的项目使用egg.js框架搭建的,同时采用sequelize ORM(Object Relational Mapping) 框架来做数据层面的处理
egg.js形成的项目,文件夹对应的作用十分明确,下面主要用到了router / controller / service
一 开心使用sequelize (找个最简单没啥重要信息的接口示范下)
路由:
'use strict'
/**
* @param {Egg.Application} app - egg application
*/
module.exports = ({
router,
controller: {
soFileManage: {
soFileManage,
},
},
}) => {
// 上传so文件列表
router.get('/so_file/manage', soFileManage.soFileList)
}
controller (Controller 负责解析用户的输入,处理后返回相应的结果,一般在这里接收参数,拿到数据暴露出去)
'use strict'
const baseController = require('../baseController')
const fs = require('fs')
class soFileManageController extends baseController {
/**
* 上传so文件列表
*/
async soFileList() {
const { ctx } = this
const { limit, offset } = this.paginationDeal(ctx.request.query)
const { list, total } = await this.service.soFileManage.soFileManage.soFileList({ limit, offset })
this.success({ data: { list, total } })
}
}
module.exports = soFileManageController
service (Service 就是在复杂业务场景下用于做业务逻辑封装的一个抽象层, 我们主要是从数据库拿数据,对对应的数据排序,联表,查询)
'use strict'
const Service = require('egg').Service
class soFileManageService extends Service {
/**
* 上传so文件列表
* @param { Object } object 传入的对象
* @param { String } object.limit 限制的返回的数据行数
* @param { String } object.offset 返回行之前忽略多少行
*/
async soFileList({ limit, offset }) {
const { rows, count } = await this.ctx.model.哈哈哈哈表名.findAndCountAll({
attributes: { exclude: [ 'updatedAt' ] },
order: [[ 'created_at', 'DESC' ]],
offset,
limit,
})
return { list: rows, total: count }
}
}
module.exports = soFileManageService
二 遇到了sequelize框架解决不了的问题 (使用sql)
比如我们的业务经常会涉及到汇总,跨库查询,那么必然少不了自己写sql,因为框架貌似实现不了,如何使用sequelize做防止sql注入呢?
2.1什么是sql注入
SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息
虽然我sql也用的不是很多,我用自己的话翻译一下: 平时我们写sql无非查数据就是select xxx from 某表 where 某字段 = yyy 这样去查,了解sql的人其实知道基本用法,如果我在输入框里面写上搜索数+一些sql的拼接,这样能够得到意黑客想要拿到的数据
那么怎么解决这个问题?我了解的有两种:
1.做输入校验
我们确实可以做基本的校验,但是不可能每个输入框针对各种输入符号进行校验
2.预编译防止SQL注入
在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题
大白话解释一下: SQL执行前,Sql先发送数据库进行编译,那么在sql当中有一些重要输入数据,我们先用一个符号占用他,等真正执行的时候,再把重要的真实数据套进去,那么即使我在输入框写各种sql,编译操作已经完成了,输入的东西也只是当成数据,不会再重新编译了,
2.2 模拟sql注入
这里我直接用工具模拟,如果在项目中模拟,假数据太费事
1 我在a表里面能够查到两条数据
2 假设你想获取id为1的数据,下面似乎没有问题,输入1
3.但是当你添加上sql的时候.输入值为 1 or id = 2
直接查到的就是两条数据,虽然数据是我模拟的,我自然知道id=1,id=2,这些都不是问题,只要记住一个问题就是我输入sql,已经恶意篡改了原本服务端写的sql的本意,还有更加强大的恶意篡改
三 在sequelize防止sql注入写法(预编译防止SQL注入)
基本用法
这里主要举例,sequelize ORM实现的方式
链接在此: sequelize中的预编译防止SQL注入3.1 没有做防止注入之前(实例)
controller
前端输入了timeBegin, timeEnd开始时间和结束时间,也就意味着时间参数不做防注入,会导致sql修改
async userData() {
const { ctx } = this
const { timeBegin, timeEnd } = this.paginationDeal(ctx.request.query)
let time = ''
if (timeBegin && timeEnd) {
time = `AND hx_user.created_at between '${timeBegin}' and '${timeEnd}'`
}
const list = await this.service.userManage.userInfoList.userData(time)
this.success({ data: {
list,
} })
}
service (汇总信息已删除,只展示查询)
async userData(time) {
return await this.app.model.query(
`select count(1) '某字段'
FROM 某A表 AS hx_user LEFT OUTER JOIN 某B表 AS basic
ON 某A表.id = 某B表.user_id WHERE hx_user.is_virtual = 0 ${time};`,
{ type: 'SELECT' })
}
上面可以看到timeBegin,timeEnd只直接写入的
3.2防注入修改后代码 (?占位/:key占位)
controller
async userData() {
const { ctx } = this
const { timeBegin, timeEnd } = this.paginationDeal(ctx.request.query)
const list = await this.service.userManage.userInfoList.userData(timeBegin, timeEnd)
this.success({ data: {
list,
} })
}
方式一,用?替代符号占位,用数组进行匹配 service
async userData(timeBegin, timeEnd) {
let timeSql = ''
let params = []
if (timeBegin && timeEnd) {
timeSql = 'AND hx_user.created_at between ? AND ?'
params = [ timeBegin, timeEnd ]
}
return await this.app.model.query(
`select count(1) 'registerUser',
FROM 某A表 AS hx_user LEFT OUTER JOIN 某B表 AS basic ON 某A表.id = 某B表.user_id
WHERE hx_user.is_virtual = 0 ${timeSql};`,
{ replacements: params, type: 'SELECT' })
}
方式二 用:key占位,用对象进行匹配 service
async userData(timeBegin, timeEnd) {
let timeSql = ''
if (timeBegin && timeEnd) {
timeSql = 'AND hx_user.created_at between :timeBegin AND :timeEnd'
}
return await this.app.model.query(
`select count(1) '某字段'
FROM 某A表 AS hx_user LEFT OUTER JOIN 某B表 AS basic ON 某A表.id = 某B表.user_id
WHERE hx_user.is_virtual = 0 ${timeSql};`,
{ replacements: { timeBegin, timeEnd }, type: 'SELECT' })
}
3.3两种方式匹配有什么异同
like这里是模糊查询用法哦,重在对比
本来我采用的是?占位,后面全部改成了:key占位.为什么呢?
如果是?和数组的方式进行占位,有一些缺点,我测试了不少,总结如下
1.很多个?????的时候,你根本不知道这个?对应的是哪个
2.数组与?有着严格的顺序,中途插一个你还要看看位置对了没
3.假设我有3个?,必须对应数组里面三个值,数组有4个行吗?不行的,也就是占位的数据要做判断,进不进行push都要做判断,而:key的方式,只要你没有某个对象名没有占位,不匹配replacements里面的值就行了
综上,:key与对象的方式更加合适,但是如果只是对一个数替换,也没有判断,数组也行
四 使用中遇到的问题
因为拿到字段数据联表和拿到数据总数分开写的,当时为了测试只在第一个加了replacements,本以后后面再加在总数那里没事,结果一直报错,报错还很不明显,如果有类似写法的,直接两个都加上再测试吧~