是否存在与PHP的mysql_real_escape_string()相当的Java?
这是为了在将它们传递给Statement.execute()之前逃避SQL注入尝试。
我知道我可以使用PreparedStatement,但我们假设这些是一次性语句,因此准备它们将导致lower performance。我已经将代码更改为使用PreparedStatement,但考虑到现有代码的结构,escape()函数会使代码更改更容易查看和维护;我更喜欢易于维护代码,除非有一个令人信服的理由来增加复杂性。此外,PreparedStatements由数据库以不同方式处理,因此这可能会使我们面临数据库中我们之前未遇到的错误,在发布到生产之前需要进行更多测试。
Apache StringEscapeUtils escapeSQL()只能使用单引号。
后记: 在我继承的环境中有许多细微之处,我在我的问题中故意避免。
要考虑两点:
1)准备好的声明不是灵丹妙药,也不能提供100%的SQL注入保护。某些数据库驱动程序使用不安全的字符串连接实例化参数化查询,而不是将查询预编译为二进制形式。此外,如果您的SQL依赖于存储过程,则需要确保存储过程本身不会以不安全的方式构建查询。
2)大多数预准备语句实现将语句绑定到语句被实例化的数据库连接上。如果您使用的是数据库连接池,则需要注意 使用预准备语句引用仅与其准备的连接一起使用。一些池化机制确实透明地实现了这一点。否则,您也可以汇集预准备语句或(最简单但更多开销)为每个查询创建一个新的预准备语句。
答案 0 :(得分:12)
据我所知,没有“标准”方法可以做到。
尽管您目前担心,我强烈建议您使用准备好的陈述。性能影响可以忽略不计 - 我们有类似的情况,每秒有几千个语句 - 其中大多数也是一次性的。
您获得的安全性应该比您尚未看到的性能问题高得多。在我看来,这是“不要过早优化”的明确情况。
在任何情况下,如果您真的发现性能问题,请确保准备好的语句通过仔细分析然后寻找备选方案来确定原因。那么你应该省去试图逃避正确的麻烦。
这更为重要,因为我推断您正在开发某种面向公众的网站 - 内部应用程序很少获得足够的流量来关注性能。
答案 1 :(得分:6)
以下是一些可以实现您所需要的代码。最初是在Vnet Publishing wiki上。
/**
* Mysql Utilities
*
* @author Ralph Ritoch <rritoch@gmail.com>
* @copyright Ralph Ritoch 2011 ALL RIGHTS RESERVED
* @link http://www.vnetpublishing.com
*
*/
package vnet.java.util;
public class MySQLUtils {
/**
* Escape string to protected against SQL Injection
*
* You must add a single quote ' around the result of this function for data,
* or a backtick ` around table and row identifiers.
* If this function returns null than the result should be changed
* to "NULL" without any quote or backtick.
*
* @param link
* @param str
* @return
* @throws Exception
*/
public static String mysql_real_escape_string(java.sql.Connection link, String str)
throws Exception
{
if (str == null) {
return null;
}
if (str.replaceAll("[a-zA-Z0-9_!@#$%^&*()-=+~.;:,\\Q[\\E\\Q]\\E<>{}\\/? ]","").length() < 1) {
return str;
}
String clean_string = str;
clean_string = clean_string.replaceAll("\\\\", "\\\\\\\\");
clean_string = clean_string.replaceAll("\\n","\\\\n");
clean_string = clean_string.replaceAll("\\r", "\\\\r");
clean_string = clean_string.replaceAll("\\t", "\\\\t");
clean_string = clean_string.replaceAll("\\00", "\\\\0");
clean_string = clean_string.replaceAll("'", "\\\\'");
clean_string = clean_string.replaceAll("\\\"", "\\\\\"");
if (clean_string.replaceAll("[a-zA-Z0-9_!@#$%^&*()-=+~.;:,\\Q[\\E\\Q]\\E<>{}\\/?\\\\\"' ]"
,"").length() < 1)
{
return clean_string;
}
java.sql.Statement stmt = link.createStatement();
String qry = "SELECT QUOTE('"+clean_string+"')";
stmt.executeQuery(qry);
java.sql.ResultSet resultSet = stmt.getResultSet();
resultSet.first();
String r = resultSet.getString(1);
return r.substring(1,r.length() - 1);
}
/**
* Escape data to protected against SQL Injection
*
* @param link
* @param str
* @return
* @throws Exception
*/
public static String quote(java.sql.Connection link, String str)
throws Exception
{
if (str == null) {
return "NULL";
}
return "'"+mysql_real_escape_string(link,str)+"'";
}
/**
* Escape identifier to protected against SQL Injection
*
* @param link
* @param str
* @return
* @throws Exception
*/
public static String nameQuote(java.sql.Connection link, String str)
throws Exception
{
if (str == null) {
return "NULL";
}
return "`"+mysql_real_escape_string(link,str)+"`";
}
}
答案 2 :(得分:5)
不要认为PreparedStatements较慢。试一试,测量它,然后判断。
PreparedStatements 始终总是优先于Statement使用,几乎毫无例外,尤其当SQL注入攻击是你想要避免的时候。
答案 3 :(得分:3)
避免SQL注入的唯一合理方法是使用预准备/参数化语句。
例如,您出于某种原因试图避免的PreparedStatement。如果你做一次性陈述,准备它们的时间应该可以忽略不计(“一次性”和“性能关键”是一个矛盾,恕我直言)。如果你在循环中做事,准备好的语句甚至会导致性能提高。
答案 4 :(得分:2)
org.apache.commons.lang.StringEscapeUtils.class可以解决您的问题!
答案 5 :(得分:0)
根据Daniel Schneller的说法,Java中没有标准的方法来处理PHP的mysql_real_escape_string() 我所做的是链接replaceAll方法来处理可能需要避免任何异常的每个方面。这是我的示例代码:
public void saveExtractedText(String group,String content)
{
try {
content = content.replaceAll("\\", "\\\\")
.replaceAll("\n","\\n")
.replaceAll("\r", "\\r")
.replaceAll("\t", "\\t")
.replaceAll("\00", "\\0")
.replaceAll("'", "\\'")
.replaceAll("\\"", "\\\"");
state.execute("insert into extractiontext(extractedtext,extractedgroup) values('"+content+"','"+group+"')");
} catch (Exception e) {
e.printStackTrace();
}
state.execute("insert into extractiontext(extractedtext,extractedgroup) values('"+content+"','"+group+"')");
} catch (Exception e) {
e.printStackTrace();
}
答案 6 :(得分:0)
除了PreparedStatement之外,我不会信任任何其他东西来确保安全。但是,如果在构建查询时需要类似的工作流程,则可以使用以下代码。它在下面使用了PreparedStatement,像StringBuilder一样工作,添加了转义函数并为您跟踪参数索引。可以这样使用:
SQLBuilder sqlBuilder = new SQLBuilder("update ").append(dbName).append(".COFFEES ");
sqlBuilder.append("set SALES = ").escapeString(sales);
sqlBuilder.append(", TOTAL = ").escapeInt(total);
sqlBuilder.append("where COF_NAME = ").escapeString(coffeeName);
sqlBuilder.prepareStatement(connection).executeUpdate();
代码如下:
class SQLBuilder implements Appendable {
private StringBuilder sqlBuilder;
private List<Object> values = new ArrayList<>();
public SQLBuilder() {
sqlBuilder = new StringBuilder();
}
public SQLBuilder(String str)
{
sqlBuilder = new StringBuilder(str);
}
@Override
public SQLBuilder append(CharSequence csq)
{
sqlBuilder.append(csq);
return this;
}
@Override
public SQLBuilder append(CharSequence csq, int start, int end)
{
sqlBuilder.append(csq, start, end);
return this;
}
@Override
public SQLBuilder append(char c)
{
sqlBuilder.append(c);
return this;
}
// you can add other supported parameter types here...
public SQLBuilder escapeString(String x)
{
protect(x);
return this;
}
public SQLBuilder escapeInt(int x)
{
protect(x);
return this;
}
private void escape(Object o)
{
sqlBuilder.append('?');
values.add(o);
}
public PreparedStatement prepareStatement(Connection connection)
throws SQLException
{
PreparedStatement preparedStatement =
connection.prepareStatement(sqlBuilder.toString());
for (int i = 0; i < values.size(); i++)
{
Object value = values.get(i);
// you can add other supported parameter types here...
if (value instanceof String)
preparedStatement.setString(i + 1, (String) value);
else if (value instanceof Integer)
preparedStatement.setInt(i + 1, (Integer) value);
}
return preparedStatement;
}
@Override
public String toString()
{
return "SQLBuilder: " + sqlBuilder.toString();
}
}