我正在开发一个包含如下代码的项目:
String sql = "SELECT MAX(" + columnName + ") FROM " + tableName;
PreparedStatement ps = connection.prepareStatement(sql);
有什么方法可以更改此代码,以便FindBugs停止给我一个 “安全性 - 准备好的语句是从非常量字符串生成的”警告?
请假设此代码对SQL INJECTION是安全的,因为我可以控制代码中的其他位置 “tableName”和“columnName”的值(它们不直接来自用户输入)。
答案 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();