简介文章主要内容包括:1.Java持久层技术/框架简介2.不同场景/框架如何编写SQL注入3.如何避免和修复SQL注入JDBC简介JDBC:1.全称JavaDatabaseConnectivity21.是Java访问数据库的API,不依赖于特定的数据库(database-independent),例如//concatsqlStringsql="SELECT*FROMusersWHEREname='"+name+"'";Statementsstmt=connection.createStatement();ResultSetrs=stmt.executeQuery(sql);安全的写法是使用参数化查询(parameterizedqueries),即在SQL语句中使用参数绑定(?占位符)和PreparedStatement,如://use?tobindvariablesStringsql="SELECT*FROMusersWHEREname=?";PreparedStatementps=connection.prepareStatement(sql);//参数索引从1ps开始。设置字符串(1,名称);在某些情况下,比如orderby和columnname,不能使用参数绑定。在这种情况下,需要手动过滤。比如orderby的字段名通常是有限制的,所以可以使用白名单的方式来限制参数值。这里需要注意的是,使用PreparedStatement并不代表不会发生注入。如果在使用PreparedStatement之前有拼接sql语句,还是会造成注入,比如//拼接sqlStringsql="SELECT*FROMusersWHEREname='"+name+"'";PreparedStatementps=connection.prepa重述(sql);一般情况下,用户的输入作为参数值,但在SQL注入中,用户的输入作为SQL命令的一部分,由数据库编译/解释执行。使用PreparedStatement时,它有一个占位符带符号(?)的sql语句只会被编译一次,然后执行只是将占位符替换为用户输入,不会再次编译/解释,所以SQL注入问题是从根本上杜绝。更详细准确的解答请参考:https://stackoverflow.com/a/34126564/6467552Mybatis介绍1、一流的持久化框架2、介于JDBC(rawSQL)和Hibernate(ORM)之间3、最简化JDBC代码,手动设置参数并获取结果4.灵活,用户可以完全控制SQL,支持高级映射更多信息请参考:http://www.mybatis.org说明在MyBatis中,使用XML文件或Annotation进行配置并将映射、接口和JavaPOJO(PlainOldJavaObjects)映射到数据库记录XML示例MapperInterface@MapperpublicinterfaceUserMapper{UsergetById(intid);}XML配置文件SELECT*FROMuserWHEREid=#{id}注解示例@MapperpublicinterfaceUserMapper{@Select("SELECT*FROMuserWHEREid=#{id}")UsergetById(@Param("id")intid);}可见,用户需要自己写SQL语句,所以使用不当会造成注入p问题。与使用JDBC不同,MyBatis使用#{}和${}来替换参数值。当使用#{}语法时,MyBatis会自动生成PreparedStatement,使用Parameterbinding(?)设置值,上面两个例子的等价JDBC查询代码如下:Stringsql="SELECT*FROMusersWHEREid=?";PreparedStatementps=connection.prepareStatement(sql);ps.setInt(1,id);因为这个#{}可以有效的防止SQL注入。具体可以参考http://www.mybatis.org/mybatis-3/sqlmap-xml.html的StringSubstitution部分。使用${}语法时,MyBatis会直接注入原始字符串,相当于拼接字符串,会导致SQL注入,如:SELECT*FROMuserWHEREname='${name}'limit1namevalue'or'1'='1,实际执行的语句为:SELECT*FROMuserWHEREname=''or'1'='1'limit1因此,建议尽量使用#{},但是有时候,比如orderby语句,使用#{}会报错,比如:ORDERBY#{sortBy}sortBy参数值为name,替换后会变成:ORDERBY"name"是按字符串"name"排序,而不是按name字段排序。具体可以参考https://stackoverflow.com/a/32996866/6467552这种情况下需要使用${}ORDERBY${sortBy}。使用${}后,用户需要自己过滤输入。sortBy的允许取值,比如只有name和email字段,异常情况下设置为默认值name在xml配置文件中,使用if标签判断Mapper接口方法ListgetUserListSortBy(@Param("sortBy")StringsortBy);xml配置文件SELECT*FROMuser或者derby${sortBy}因为Mybatis不支持else,需要一个默认值,可以使用choose(when,otherwise)SELECT*FROMuserorderby${sortBy}orderbyname更多除了orderby,还有一些情况可能会用到${},可以用其他方法避免,比如:如果like语句需要使用通配符(通配符%和_),可以在两者上都加上在代码层%两边的参数值,然后使用#{}使用bind标签构造新的参数,然后使用#{}Mapper接口方法ListgetUserListLike(@Param("name")Stringname);xml配置文件SELECT*FROMuserWHEREnameLIKE#{pattern}语句中的值为OGNL表达式,具体参考http://www.mybatis.org/mybatis-3/dynamic-sql.htmlbind部分使用SQLconcat()函数SELECT*FROMuserWHEREnameLIKEconcat('%',#{name},'%')除了注入问题,这里需要过滤用户输入,不是通配符否则,当表中数据量很大时,假设用户输入的是%%,就会对整个表进行模糊查询,严重时可能导致DOS。参考:http://www.tothenew.com/blog/sql-wildcards-is-your-application-safeIN条件使用和#{}Mapper接口方法ListgetUserListIn(@Param("nameList")ListnameList);xml配置文件SELECT*FROMuserWHEREnamein#{name}详见http://www.mybatis.org/mybatis-3/dynamic-sql.htmlForeach部分限制语句1.直接使用#{}即可2.Mapper接口方法ListgetUserListLimit(@Param("offset")intoffset,@Param("limit")intlimit);xml配置文件SELECT*FROMuserlimit#{offset},#{limit}JPA&Hibernate简介JPA:1、全称JavaPersistenceAPI2,ORM(object-relationalmapping)持久层API,具体实现请参考https://en.wikipedia.org/wiki/JavaPersistenceAPIHibernate:JPAORMimplementation请参考http://hibernate.org获取更多信息。这里有一个错误的理解。如果使用ORM框架,就不会出现SQL注入。事实上,在Hibernate中,支持HQL(HibernateQueryLanguage)和原生sql查询。前者有HQL注入,后者有和前面JDBC一样的注入问题。让我们看一下HQLHQL查询示例Queryquery=session.createQuery("fromUserwherename='"+name+"'",User.class);Useruser=query.getSingleResult();这里的User是类名,类似于原生SQL,拼接会导致注入的正确用法:位置参数(Positionalparameter)Queryquery=session.createQuery("fromUserwherename=?",User.class);query。setParameter(0,name);命名参数(namedparameter)Queryquery=session.createQuery("fromUserwherename=:name",User.class);query.setParameter("name",name);命名参数列表(命名参数列表)Queryquery=session.createQuery("fromUserwherenamein(:nameList)",User.class);query.setParameterList("nameList",Arrays.asList("lisi","zhaowu"));Classinstance(JavaBean)Useruser=newUser();user.setName("zhaowu");Queryquery=session.createQuery("fromUserwherename=:name"t;,User.class);//User类需要有getName()方法query.setProperties(user);NativeSQL存在SQL注入Stringsql="select*fromuserwherename='"+name+"'";//deprecated//Queryquery=session.createSQLQuery(sql);Queryquery=session.createNativeQuery(sql);使用参数绑定设置参数值Stringsql="select*fromuserwherename=:name";//deprecated//Queryquery=session.createSQLQuery(sql);Queryquery=session.createNativeQuery(sql);query.setParameter("name",姓名);JPA在JPA中使用了JPQL(JavaPersistenceQueryLanguage),同时也支持原生sql,所以和Hibernate也有类似的问题,这里就不多说细说了