如何避免“安全性 - 从非常量字符串生成预准备语句”FindBugs警告

时间:2012-05-08 14:10:32

标签: java jdbc findbugs

我正在开发一个包含如下代码的项目:

String sql = "SELECT MAX(" + columnName + ") FROM " + tableName;                
PreparedStatement ps = connection.prepareStatement(sql);

有什么方法可以更改此代码,以便FindBugs停止给我一个 “安全性 - 准备好的语句是从非常量字符串生成的”警告?

请假设此代码对SQL INJECTION是安全的,因为我可以控制代码中的其他位置 “tableName”和“columnName”的值(它们不直接来自用户输入)。

7 个答案:

答案 0 :(得分:5)

不要将sql字符串连接到+。你可以使用

String sql = String.format("SELECT MAX(%s) FROM %s ", columnName, tableName);

这比串联一个字符串慢,所以你应该初始化这个static然后这不是问题。

我认为使用StringBuilder也会修复此警告。

另一种可以避免此警告的方法是在该字符串(或方法/或类)之上添加@SuppressWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")

您还可以使用Filter File来定义应排除的规则。

答案 1 :(得分:5)

private static final String SQL = "SELECT MAX(?) FROM ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.preparedStatement.setInt(1,columnName);
ps.preparedStatement.setString(2,tableName);

如果你正在使用预准备语句,那么in参数应该是一个最后的字符串,稍后应该使用setInt,setString方法添加参数。

这将解决查找警告。

答案 2 :(得分:1)

尝试使用以下内容......

private static final String SQL = "SELECT MAX(%s) FROM %s";

然后在使用它时使用String.format()调用...

PreparedStatement ps = connection.prepareStatement(String.format(sql,columnName,tableName));

如果这不能解决问题,您可以随时忽略该检查;在FindBugs配置中将其关闭。

如果这不起作用(或者不是一个选项),某些IDE(如IntelliJ)也会让你用特殊格式的注释或注释来抑制警告。

答案 3 :(得分:1)

String.format和StringBuilder(或StringBuffer)都没有帮助我。

解决方案是“prepareStatement”隔离:

private PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException {
    return conn.prepareStatement(sql);
}

答案 4 :(得分:1)

可以使用串联来创建String。这样做不会导致安全警告。在处理长SQL语句时,为了清晰起见,这是更好的选择,这些语句可以更好地分割成多行

使用变量构造String是导致安全警告的原因。

这将导致警告:

String columnName = getName();
String tableName  = getTableName();
final String sql = "SELECT MAX(" + columnName + ") FROM " + tableName;
PreparedStatement ps = connection.prepareStatement(sql);

这不会起作用:

String columnName = getName();
String tableName  = getTableName();
final String sql = "SELECT MAX(" + "?" + ")" +
                   "FROM " + "?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, columnName);
ps.setString(2, tableName);

它不起作用,因为预准备语句只允许将参数绑定到SQL语句的“值”位。

这是有效的解决方案:

private static final boolean USE_TEST_TABLE = true;
private static final boolean USE_RESTRICTED_COL = true;
private static final String TEST_TABLE = "CLIENT_TEST";
private static final String PROD_TABLE = "CLIENT";
private static final String RESTRICTED_COL ="AGE_COLLATED";
private static final String UNRESTRICTED_COL ="AGE";

....................

final String sql = "SELECT MAX(" +
        ( USE_RESTRICTED_COL ? RESTRICTED_COL : UNRESTRICTED_COL ) +  ")" +
        "FROM " +
        ( USE_TEST_TABLE ? TEST_TABLE : PROD_TABLE );
PreparedStatement ps = connectComun.prepareStatement(sql);

但是只有在必须在编译时知道其名称的两个表之间进行选择时,它才有效。你可以使用复合三元运算符超过2个案例但随后变得不可读。

如果getName()或getTableName()从不受信任的来源获取名称,则第一种情况可能是安全问题。

如果先前已验证过这些变量,则很有可能使用变量构造安全的SQL语句。这是你的情况,但FindBugs无法解决这个问题。 Findbugs无法知道哪些来源可信任。

但是,如果您必须使用来自用户或不受信任的输入的列或表名称,则无法绕过它。您必须验证自己的字符串,并使用其他答案中提出的任何方法忽略Findbugs警告。

结论:对于这个问题的一般情况,没有完美的解决方案。

答案 5 :(得分:0)

如果您确定不可能注入SQL,请在方法上使用 SuppressFBWarnings 注释:

@edu.umd.cs.findbugs.annotations.SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")

答案 6 :(得分:0)

StringBuilder sql = new StringBuilder();
sql.append("SELECT MAX(")
   .append(columnName)
   .append(") FROM ")
   .append(tableName);

PreparedStatement ps = connection.prepareStatement(sql);
ps.execute();