如果我们的SQL语句是固定的,但是参数会不断变化,那我们要怎么办呢?
首先看看以下的处理方式:
statement.executeQuery("select * from user where userid = "+userInputId);
看起来我们是解决了我们的需要,但是其实这种写法会带来很严重的问题。
加入我们的userInputId是
1 or 1=1,那么上面的SQL就会编程:
select * from user where userid = 1 or 1=1
这样,无论userid 为多少,我们都会返回所有用户记录。
这种动态SQL会造成我们著名的SQL注入(SQL Injection)的安全漏洞。这种漏洞除了可以窃取你的数据库记录外,严重的时候更能够破坏你的数据库结构,让你的数据库彻底毁掉。例如:
statement.execute("insert int user values(......"+userInput+")");
如果userInput是:
null);delete from user;--
那么,就会执行两条SQL命令,第一条就是创建一个user记录,第二条就会把所有user记录删掉!!
为了解决这种参数会变化但是SQL结构固定的动态SQL语句,jdbc提供了PreparedStatment这种操作方式:
java.sql.PreparedStatement ps = conn.preparedStatement("select * from user where userid = ?");
...
ps.setInt(1,userInputId);
ResultSet rs = ps.executeQuery();
...
这个接口会自动把一些非法字符串进行转换,防止用户串改操作语句。
而且使用PreparedStatement还有利于提高数据库效率,对于数据库,执行以下的两条SQL:
select * from user where userid = 1
select * from user where userid = 2
它会视为两条不同的SQL来执行,并且把执行过程分别存储起来,但是对于preparedStatement,它会记录:
select * from user where userid = ?
每次会根据参数返回操作结果,无论执行多少次都只会保留一份操作副本,能够有效节省数据库的编译SQL性能。
但是PreparedStatement也不是万能,假如我们的变化不是限制在参数上,就有问题,例如我们获取的数据表可能是动态的,或者where条件是动态的。那么我们怎么来处理这种情况并且有效防止SQL Injection呢?
很简单,但是却比较麻烦,就是限制用户的输入,并且不要直接把用户输入直接作为SQL凭借部分。
应用实例:
根据用户输入读取不同数据表的所有信息,要求:
用户输入:user时,获取所有用户表信息(user表)
用户输入:org时,获取所有组织信息(org表)
用户输入其它信息:返回所有新闻记录(news表)
实现:
String sql = "select * from news";
if(userInput != null && userInput.equalsIgnoreCase("user"))
sql = "select * from user";
if(userInput != null && userInput.equalsIgnoreCase("org"))
sql = "select * from org";
利用这种方法和PreparedStatement就能够有效防止SQL注入。
无论日后是否使用ORM工具来操作数据库,都必须小心不要造成SQL注入漏洞,否则后果不堪设想。