Java 如何避免;安全性-准备好的语句由非恒定字符串“生成”;FindBugs警告

Java 如何避免;安全性-准备好的语句由非恒定字符串“生成”;FindBugs警告,java,jdbc,findbugs,Java,Jdbc,Findbugs,我正在从事一个项目,其代码如下: String sql = "SELECT MAX(" + columnName + ") FROM " + tableName; PreparedStatement ps = connection.prepareStatement(sql); 有没有什么方法可以改变这个代码,让FindBugs停止给我一个 “安全性-从非恒定字符串生成准备好的语句”警告 请假设这段代码在SQL注入方面是安全的,因为我可以控制代码中的其他地方 “t

我正在从事一个项目,其代码如下:

String sql = "SELECT MAX(" + columnName + ") FROM " + tableName;                
PreparedStatement ps = connection.prepareStatement(sql);
有没有什么方法可以改变这个代码,让FindBugs停止给我一个 “安全性-从非恒定字符串生成准备好的语句”警告

请假设这段代码在SQL注入方面是安全的,因为我可以控制代码中的其他地方
“tableName”和“columnName”的值(它们不直接来自用户输入)。

不通过
+
连接
sql
字符串。你可以用

String sql = String.format("SELECT MAX(%s) FROM %s ", columnName, tableName);
这比串接字符串要慢,因此您应该初始化此
静态
,这样就没有问题了

我认为使用
StringBuilder
也可以修复此警告

可以避免此警告的另一种方法是在该字符串(或方法/或类)上方添加
@SuppressWarnings(“SQL\u PREPARED\u STATEMENT\u GENERATED\u FROM\u NONCONSTANT\u STRING”)


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

尝试使用以下命令

private static final String SQL = "SELECT MAX(%s) FROM %s";
然后在使用String.format()时调用它

PreparedStatement ps = connection.prepareStatement(String.format(sql,columnName,tableName));
如果这不能解决问题,你可以忽略检查;在FindBugs配置中关闭它


如果这不起作用(或不是一个选项),某些IDE(如IntelliJ)也会允许您使用特殊格式的注释或批注来提供警告。

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

溶液为“预陈述”分离:

private PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException {
    return conn.prepareStatement(sql);
}
若您使用的是prepared语句,那个么in参数应该是一个最终字符串,稍后应该使用setInt、setString方法添加参数


这将解决findbug警告。

如果确保不存在SQL注入的可能性,请在方法上使用SuppressFBWarnings注释:

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

可以使用连接来创建字符串。这样做不会导致安全警告。在处理长SQL语句时,为了清晰起见,最好将其拆分为多行

使用变量构造字符串是导致安全警告的原因

这将导致警告:

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);
但是,只有在编译时必须在两个名称已知的表之间进行选择时,它才起作用。您可以在两种以上的情况下使用复合三元运算符,但这样就无法读取

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

如果先前已经验证了变量,那么使用变量构造安全的SQL语句是完全可能的。这是你的情况,但FindBugs无法理解。Findbugs无法知道哪些源是可信的或不可信的

但若您必须使用来自用户或不受信任的输入的列或表名,那个么就并没有办法了。您必须自己验证这样的字符串,并使用其他答案中建议的任何方法忽略Findbugs警告


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

嗨,杰西,谢谢你的回答。我试着照你说的做,但不幸的是没有成功。你的最后一个建议,与@user714965提到的相同,起到了作用。现在我必须决定是否要为在我的项目中使用FindBugs lib来消除警告而付出代价。我在使用StringBuilder时遇到过此警告的其他情况,因此这不起作用。注释是有效的(最好向其他读者澄清,它的完全限定名是@edu.umd.cs.findbugs.annotations.SuppressWarnings)。现在,我必须决定是否在我的项目中使用FindBugs库。再次感谢。@ederribeiro:项目中对FindBugs的依赖可能不是最好的,你是对的。在我最初的回答中,我遗漏了一点,请看最后一段。这解决了这个问题,因为OP确信他的代码不会被SQL注入。但它容易出错,因此通常是一种不好的做法。准备好带有参数的语句是一种方法。我不会投反对票,因为它实际上解决了问题,但我建议不要那样做。这对我也没有帮助。你的声明执行了吗?获取的绑定表名
ORA-00903:无效的表名
。传递列名是有效的,但是您可以返回列名(而不是列值)。这是NOGO!你好,何塞·安东尼奥·杜拉·奥尔莫斯谢谢你的建议,但是,你试过了吗?似乎不可能使用prepared语句设置“元数据”(如表名或列名)。我尝试过mysql和h2,但没有成功。该线程中还有一个用户尝试使用Oracle,但也失败了。我有一个生产代码,可以通过这种方式更改表名。我还没有检查这个特定的代码,但我会这样做。这就是内存不足时的情况。我需要在两个表中进行选择,一个用于生产,另一个用于测试。但它并没有以这种方式工作,因为表名和列名不能是变量;语句预编译需要知道用于验证sintax正确性的表名和列名。因此,它们不可能是可变的。我找到了一个解决方案,它仍然允许我在没有得到警告的情况下使用连接。我已经用这个解决方案更新了我的答案。出于两个原因,这甚至不会编译。你为什么还要在这里使用
StringBuilder
?这毫无意义。
StringBuilder sql = new StringBuilder();
sql.append("SELECT MAX(")
   .append(columnName)
   .append(") FROM ")
   .append(tableName);

PreparedStatement ps = connection.prepareStatement(sql);
ps.execute();
StringBuilder sql = new StringBuilder();
sql.append("SELECT MAX(")
   .append(columnName)
   .append(") FROM ")
   .append(tableName);

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