PreparedStatement类是java的一个类,准确说是jdbc规范中的一个接口,各个数据库产商的实现不同(即实现类不同),今天我们就以mysql数据库来说,我已经下载了mysql数据库的驱动jar包和驱动程序的源代码.
sql注入的时候,比如,有些用户就会在界面上输入anything' OR 'x'='x这种东西,最后sql语句就是如下:
SELECT * FROM users WHERE userName = '令狐冲' AND password = 'anything' OR 'x'='x'
其实
SELECT * FROM users WHERE userName = ? AND password = ?
preparedStatement.setString(1,'XXX');
preparedStatement.setString(2,'XXX');
我们可以看下mysql的底层的setString();方法,其实就是用值代替了?的位置,并且在值的两边加上了单引号,然后再把值当中的所有单引号替换成了斜杠单引号,说白了就是把值当中的所有单引号给转义了!这就达到了防止sql注入的目的,说白了mysql驱动的PreparedStatement实现类的setString();方法内部做了单引号的转义,而Statement不能防止sql注入,就是因为它没有把单引号做转义,而是简单粗暴的直接拼接字符串,所以达不到防止sql注入的目的。所以,不管是mysql数据库产商的PreparedStatement实现类还是sqlserver数据库产商的PreparedStatement实现类,其实底层的原理都差不多,当然啦,我们直接在java程序中把那些单引号直接转义也行,只不过比较繁琐一点罢了,既然各个数据库产商的PreparedStatement实现类的setString()方法中已经做了转义,我们何必在java程序中自己去做转义的这部分工作呢?
你如果想看最后在数据库中执行的sql语句是什么的话,可以打印出来看看,如果是mysql数据库的话,直接System.out.println(preparedStatement.toString());
你们可以直接看看打印出来是不是SELECT * FROM users WHERE userName = '令狐冲' AND password = 'anything\' OR\'x\'=\'x'
不好意思,如果你们用的是mysql数据库的话,你们自己打印出来试一试,我没试过,但是我知道底层的原理的话,大概猜一下,只要方向猜对了,八九不离十,其实大家写程序写多了,写久了,你们就可以去猜一猜那些大牛或者是框架的底层的原理,只要方向猜对了,基本上八九不离十,所以,有时候还是要大胆的去猜测底层的原理和实现!(大胆猜测,小心求证!)
如果是sqlserver数据库的话,
System.out.println(preparedStatement.toString());
好像不会打印出具体的sql语句,因为sqlserver和mysql的preparedStatement实现类不同。至于oracle数据的话,
System.out.println(preparedStatement.toString());
会不会打印出具体的sql语句,我不太清楚,我没试过,你们可以试一试!
以下是mysql数据库的PreparedStatement实现类的setString()方法(你们可以去网上下载mysql的驱动程序的源代码,源代码里面有jdbc的PreparedStatement实现类,去看看实现类中的setString()方法吧)
/**
* Set a parameter to a Java String value. The driver converts this to a SQL
* VARCHAR or LONGVARCHAR value (depending on the arguments size relative to
* the driver's limits on VARCHARs) when it sends it to the database.
*
* @param parameterIndex
* the first parameter is 1...
* @param x
* the parameter value
*
* @exception SQLException
* if a database access error occurs
*/
public synchronized void setString(int parameterIndex, String x) throws SQLException {
// if the passed string is null, then set this column to null
if (x == null) {
setNull(parameterIndex, Types.CHAR);
} else {
checkClosed();
int stringLength = x.length();
if (this.connection.isNoBackslashEscapesSet()) {
// Scan for any nasty chars
boolean needsHexEscape = isEscapeNeededForString(x,
stringLength);
if (!needsHexEscape) {
byte[] parameterAsBytes = null;
StringBuffer quotedString = new StringBuffer(x.length() + 2);
quotedString.append('\'');
quotedString.append(x);
quotedString.append('\'');
if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(quotedString.toString(),
this.charConverter, this.charEncoding,
this.connection.getServerCharacterEncoding(),
this.connection.parserKnowsUnicode(), getExceptionInterceptor());
} else {
// Send with platform character encoding
parameterAsBytes = StringUtils.getBytes(quotedString.toString());
}
setInternal(parameterIndex, parameterAsBytes);
} else {
byte[] parameterAsBytes = null;
if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(x,
this.charConverter, this.charEncoding,
this.connection.getServerCharacterEncoding(),
this.connection.parserKnowsUnicode(), getExceptionInterceptor());
} else {
// Send with platform character encoding
parameterAsBytes = StringUtils.getBytes(x);
}
setBytes(parameterIndex, parameterAsBytes);
}
return;
}
String parameterAsString = x;
boolean needsQuoted = true;
if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {
needsQuoted = false; // saves an allocation later
StringBuffer buf = new StringBuffer((int) (x.length() * 1.1));
buf.append('\'');
//
// Note: buf.append(char) is _faster_ than
// appending in blocks, because the block
// append requires a System.arraycopy()....
// go figure...
//
for (int i = 0; i < stringLength; ++i) {
char c = x.charAt(i);
switch (c) {
case 0: /* Must be escaped for 'mysql' */
buf.append('\\');
buf.append('0');
break;
case '\n': /* Must be escaped for logs */
buf.append('\\');
buf.append('n');
break;
case '\r':
buf.append('\\');
buf.append('r');
break;
case '\\':
buf.append('\\');
buf.append('\\');
break;
case '\'':
buf.append('\\');
buf.append('\'');
break;
case '"': /* Better safe than sorry */
if (this.usingAnsiMode) {
buf.append('\\');
}
buf.append('"');
break;
case '\032': /* This gives problems on Win32 */
buf.append('\\');
buf.append('Z');
break;
case '\u00a5':
case '\u20a9':
// escape characters interpreted as backslash by mysql
if(charsetEncoder != null) {
CharBuffer cbuf = CharBuffer.allocate(1);
ByteBuffer bbuf = ByteBuffer.allocate(1);
cbuf.put(c);
cbuf.position(0);
charsetEncoder.encode(cbuf, bbuf, true);
if(bbuf.get(0) == '\\') {
buf.append('\\');
}
}
// fall through
default:
buf.append(c);
}
}
buf.append('\'');
parameterAsString = buf.toString();
}
byte[] parameterAsBytes = null;
if (!this.isLoadDataQuery) {
if (needsQuoted) {
parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString,
'\'', '\'', this.charConverter, this.charEncoding, this.connection
.getServerCharacterEncoding(), this.connection
.parserKnowsUnicode(), getExceptionInterceptor());
} else {
parameterAsBytes = StringUtils.getBytes(parameterAsString,
this.charConverter, this.charEncoding, this.connection
.getServerCharacterEncoding(), this.connection
.parserKnowsUnicode(), getExceptionInterceptor());
}
} else {
// Send with platform character encoding
parameterAsBytes = StringUtils.getBytes(parameterAsString);
}
setInternal(parameterIndex, parameterAsBytes);
this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.VARCHAR;
}
}