上篇文章中写的JdbcUtil工具类中关于sql部分会存在安全问题:
首先有t_user这样一张表:
表中只有aa和bb两个用户。
然后运行这个测试单元:
@Test
public void testLogin() {
String username = "cc' or '1'='1";
String password = "cc' or '1'='1";
User user = selectRow("select * from t_users where username='" + username + "'and password='" + password+"'", User.class);
System.out.println(user!=null?"登录成功":"登录失败");
}
依然会显示用户登录成功,这是因为字符串拼接造成的问题,就叫sql注入。
可以使用以下JdbcUntilPlus代码:
public static <T> List<T> list(String sql, Class<T> c,Object ... params) {
// 创建一个集合,存放所有对象
List<T> tList = new ArrayList<>();
try {
// 1.注册驱动-反射去加载jar包中com.mysql.jdbc.Drvier类的DriverManager.registerDriver(new Driver());
Class.forName("com.mysql.jdbc.Driver");
// 2.获取连接对象
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/summer-camp2023?characterEncoding=utf-8", "root", "1234");
// 4.需要创建statement执行sql
PreparedStatement statement = con.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1,params[i]);
}
// 5.statement执行sql,返回结果集
ResultSet rs = statement.executeQuery();
// 结果集rs得到结果集元数据
ResultSetMetaData md = rs.getMetaData();
// 获取结果集的总列数
int columnCount = md.getColumnCount();
// 6.解析rs
while (rs.next()) {//光标向下移动一行,光标默认在列名那一行
// 根据每一行数据,封装成一个实体对象
T t = c.newInstance();
// 1.取出某一行的每个数据,封装到对象t的属性中
for (int i = 1; i < columnCount + 1; i++) {
// 通过列的序号获取每一列的值
Object value = rs.getObject(i);
if (value != null) {
// 通过列的序号获取每一列的列名
String columnName = md.getColumnName(i);
// 因为列名和实体类t中的属性名一致,为每一个属性构造一个反射中的set方法
Field f = c.getDeclaredField(columnName);
// 赋予私有属性的赋值权限
f.setAccessible(true);
// 使用反射,把value给到对象t的属性中
f.set(t, value);//理解为:把value赋值给对象t的columnName属性,相对于set方法
}
}
// 把对象存入集合中
tList.add(t);
}
// 7.关闭资源
statement.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
return tList;
}
首先使用了可变长参数params,然后在其中的sql代码部分,使用了Statement的子类PreparedStatement来提前接收sql语句,随后根据params数组的长度使用for循环分别设置指定参数的值,使用了这个方法后再调用selectRow时就可以像下面这样使用?占位符:
User user = selectRow("select * from t_users where username= ? and password= ? ", User.class,username,password);