防止sql注入的方式一般从两个方面下手,一个是代码方面。一个是数据库方面。在数据库方面比如mybatis的方式使用 #{} 就可以防止注入。
今天我们只谈谈PreparedStatement如何防止SQL注入。
PreparedStatement内置了SQL预备处理的能力
String sql= "select * from users where username=? and password=?; PreparedStatement preState = conn.prepareStatement(sql); preState.setString(1, userName); preState.setString(2, password); ResultSet rs = preState.executeQuery();
select * from users where username=? and password=?这个SQL会进行预编译,再填充参数的,使用setString方法替换?代表的参数。
但是如果不用问号代替,直接通过参数拼接SQL,那么预编译就没有意义,那就还是没有防止SQL注入。
比如
select * from tb_name = '随意' and passwd = '' or '1' = '1';
这个时候passwd输入的参数是: ' or '1' = '1
更有甚者直接输入
select * from tb_name = '随意' and passwd = '';drop table tb_name;
直接将passw的参数改成: ';drop table tb_name;
不过一般没有使用预编译的系统网站会通过拦截特殊字符的方式防止sql注入的。
下面我们改进下上面的代码,让替换的方式更加灵活。
public int update(String sql, List<?> params) { PreparedStatement ps = null; try { Connection conn = POContext.getConnection(this.dbName, this.txnName, this.timeOut); ps = conn.prepareStatement(sql); if ((params != null) && (params.size() > 0)) { for (int i = 0; i < params.size(); i++) { setParam(ps, i + 1, params.get(i)); } } return ps.executeUpdate(); } catch (Exception e) { throw new DAOException("update error!", e); } finally { clean(null, ps); } }
public void setParam(PreparedStatement ps, int idx, Object obj) { try { if ((obj instanceof Date)) { ps.setTimestamp(idx, new Timestamp(((Date)obj).getTime())); return; } if ((obj instanceof Blob)) { ps.setBlob(idx, (Blob)obj); return; } if ((obj instanceof Clob)) { ps.setClob(idx, (Clob)obj); return; } if ((obj instanceof BigDecimal)) { ps.setBigDecimal(idx, (BigDecimal)obj); return; } if ((obj instanceof BigInteger)) { ps.setBigDecimal(idx, new BigDecimal((BigInteger)obj)); return; } if ((obj instanceof Boolean)) { ps.setInt(idx, ((Boolean)obj).booleanValue() ? 0 : 1); return; } ps.setObject(idx, obj); } catch (SQLException e) { this.logger.error("ps set params error!", e); throw new DAOException("ps set params error!", e); } }
这样修改的话,替换?的方式更加灵活,只需要在List<?> params传参时,注意对应位置,不需要再每个PreparedStatement的setObject方法的第一个参数前加上替换问号的位子,
以及注意参数类型了