首先需要声明,本文纯属一个毫无远见和真才实学的小小开发人员的愚昧见解,仅供用于web系统安全方面的参考。
1、前提
很多同学都希望PreparedStatement 打印出最终执行的SQL,可能用于学习,也可能用于系统维护,也有可能用于其他的目标;
我也有这个想法和需求,但是经过多次实践和尝试,我发现在我的能力范围,我是无法实现的。
于是我找到了一个工具,log4jdbc ,这个工具能够切入JDBC层,对实际SQL执行前,把SQL抽出来。
这个工具相当强大,不过最终的运行结果会超出你的设想…….
PreparedStatement 打印出最终执行的SQL。
利用log4jdbc工具。
2、使用log4jdbc工具
详见我的博客:
好记性不如烂笔头14-使用log4jdbc显示完整SQL语句和执行时间
http://blog.csdn.net/ffm83/article/details/43407905
有详细的说明
3、测试用的JSP页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title>阿饭同学的测试</title></head><body> <form action="aLogin.action" method="post" name="form1"> <table width="392" border="1"> <tr> <td height="35"><br> <div align="center"> <p>用户名:<input type="text" name="username" size=16 ></p> <p>密码: <input type="password" name="password" size=16 > </p> </div></td> </tr> <tr align="center"> <td colspan="2" bgcolor="#FFCCFF"><input type="submit" value="登陆" /></td> </tr> </table> </form></body></html>
4、利用log4jdbc工具打印PreparedStatement的最终执行SQL的JAVA代码
package com.struts2;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import javax.servlet.http.HttpServletRequest;import org.apache.commons.dbutils.DbUtils;import org.apache.commons.lang.xwork.StringUtils;import org.apache.struts2.ServletActionContext;import com.db.DBUtils;import com.db.Log4JDBCTest;import com.opensymphony.xwork2.ActionSupport;/** * 一个简单的登陆认证功能,仅用于说明情况 * * @author 范芳铭 */public class LoginAction extends ActionSupport { private static final long serialVersionUID = 7854497526623985504L; public String execute() throws Exception { System.out.println("---LoginAction start--"); HttpServletRequest request = ServletActionContext.getRequest(); String username = request.getParameter("username"); String password = request.getParameter("password"); request.setAttribute("username", username); // 用户名和密码如果有一个为空,返回失败 if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { return "false"; } Connection con = null; try { //con = DBUtils.getConnO2O(); con = Log4JDBCTest.getConnBM(); String sql = "select count(*) as count from ffm_user where " + "username = ? and password = ? "; System.out.println("打印出来的SQL:" + sql); PreparedStatement ps = con.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs = ps.executeQuery(); while (rs.next()) { int count = rs.getInt("count"); //不考虑密码加密的问题,如果在数据库中找到一个结果,那么就当密码正确 if (count > 0) { return "success"; } else { return "false"; } } } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.closeQuietly(con); } return "success"; }}
5、启动应用,运行
输入:用户名:ffm,密码:1
打印出来的SQL:select count(*) as count from ffm_user where username = ? and password = ?
Log4jdbc打印出来的SQL:
select count(*) as count from ffm_user where username = ‘ffm’ and password = ‘1’ {executed in 147 msec}
是不是感觉很不错,确实满足了我们的需求
我们换一种输入吧。
输入:用户名:’ or 0=’0’– ,密码输入:2 或者其他任意
打印出来的SQL:select count(*) as count from ffm_user where username = ? and password = ?
Log4jdbc打印出来的SQL:
select count(*) as count from ffm_user where username = ‘’ or 0=’0’–’ and password = ‘2’ {executed in 331 msec}
看起来也很不错,但是这里有一个巨大的坑在里面。
仔细观察程序,输入:用户名:’ or 0=’0’– ,密码输入:2 是无法通过验证的,而 Log4jdbc打印出来的SQL,放在plsql中执行,是通过验证的。
也就是说,实际执行的SQL,不是我们所打印出来看到的。
6、结论
**Log4jdbc 只能打印PreparedStatement 最终执行的常规SQL,不能打印带有特殊字符的SQL。
使用PreparedStatement的参数的方法,是能够针对性的防御SQL注入的。**