JDBI 如何动态创建 WHERE 子句同时防止 SQL 注入?

问题描述 投票:0回答:4

我想动态过滤 JDBI 查询。

参数列表是通过 REST 从 UI 传递的,例如

http://localhost/things?foo=bar&baz=taz
http://localhost/things?foo=buz

它(笨拙地)构建为(Jersey @Context UriInfo::getQueryParameters -> StringBuilder),如下所示:

WHERE foo=bar AND baz=taz

并传递给JDBI,如下所示:

@UseStringTemplate3StatementLocator
public interface ThingDAO {
   @SqlQuery("SELECT * FROM things <where>)
   List<Thing> findThingsWhere(@Define("where") String where);
}

据我了解,当前的实现很容易受到 SQL 注入的攻击。 显然我可以清理列名,但不能清理值。 1

必须有一种更优雅且防 SQL 注入的方法来执行此操作。

java rest dropwizard jdbi
4个回答
8
投票

受到让-伯纳德的启发我想出了这个:

public class WhereClause {
    public HashMap<String, String> queryValues; // [<"foo","bar">, <"baz","taz">]
    public String preparedString; // "WHERE foo=:foo AND bar=:baz"
}

通过自定义 Binder 绑定

BindWhereClause
:

@BindingAnnotation(BindWhereClause.WhereClauseBinderFactory.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface BindWhereClause {
    class WhereClauseBinderFactory implements BinderFactory {
        public Binder build(Annotation annotation) {
            return new Binder<BindWhereClause, WhereClause>() {
                public void bind(SQLStatement q, BindWhereClause bind, WhereClause clause) {
                    clause.queryValues
                            .keySet()
                            .forEach(s -> q.bind(s, clause.queryValues.get(s)));
                }
            };
        }
    }
}

以及

@Define
@Bind
的组合:

@UseStringTemplate3StatementLocator
public interface ThingDAO {
   @SqlQuery("SELECT * FROM things <where>")
   List<Thing> findThingsWhere(@Define("where") String where, 
                               @BindWhereClause() WhereClause whereClause);
}

这应该是防注入的。 (是吗?)


1
投票

使用参数化查询。 这里是他们的 jdbi 页面。
参数化查询是大多数设置中防止 SQL 注入的方法。

您可以动态创建 where 语句,但保留参数名称而不是值,它们稍后将以安全的方式进行绑定。

您可能会对这一点特别感兴趣,因为您的参数是动态的:

@BindingAnnotation(BindSomething.SomethingBinderFactory.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface BindSomething 
{ 

  public static class SomethingBinderFactory implements BinderFactory
  {
    public Binder build(Annotation annotation)
    {
      return new Binder<BindSomething, Something>()
      {
        public void bind(SQLStatement q, BindSomething bind, Something arg)
        {
          q.bind("ident", arg.getId());
          q.bind("nom", arg.getName());
        }
      };
    }
  }
}

我从未使用过 jdbi,所以我不能 100% 确定你必须做什么,但看起来 q.bind(...) 方法正是你想要的。


0
投票

我知道这已经很旧了,但这是我使用 JDBI3 版本和 DAO 类的解决方案。

public interface MyEntityDAO extends SqlObject {

    default List<MyEntity> searchEntitiesWithDynamicWhere(Map<String, Object> queryParams) {
        String whereClause = buildWhereClause(queryParams);
        return searchEntities(queryParams, whereClause);
    }
    
    @SqlQuery("SELECT * FROM my_table <whereClause>")
    List<MyEntity> searchEntities(@BindMap Map<String, Object> queryParams, @Define("whereClause") String whereClause);

    default String buildWhereClause(Map<String, Object> queryParams) {
        StringBuilder whereClause = new StringBuilder();

        if (!queryParams.isEmpty()) {
            whereClause.append(" WHERE ");

            for (Map.Entry<String, Object> entry : queryParams.entrySet()) {
                if (whereClause.length() > " WHERE ".length()) {
                    whereClause.append(" AND ");
                }

                whereClause.append(entry.getKey()).append(" = :").append(entry.getKey());
            }
        }

        return whereClause.toString();
    }
}

0
投票

对于 JDBI 3,事情看起来简单得多: 请参阅:Jdbi 3 开发人员指南,6.4 绑定参数

使用绑定变量引用生成动态 SQL 查询以避免 SQL 注入漏洞。

这个例子并不是 100% 真实,因为你可能会 根据数据类型以不同的方式对待各个字段。 另外,修改输入映射有点冒险(副作用==不好)。 但它包含在这里是为了演示绑定变量 分页参数(OFFSET/LIMIT)。

警告:仍然需要测试!

public List<SomeThing> getSomeThings(Map<String, String> filter, int start, int limit) throws SQLException {
    try ( Handle handle = jdbi.open() ) {
        return handle.createQuery(buildSql(filter, start, limit))
                     .bindMap(filter) // bind by name to keys used to generate SQL
                     .map(new SomeThingRowMapper()).list();
    }
}

static String buildSql( Map<String, String> filter, int start, int limit ) {
    StringBuilder sql = new StringBuilder(256);
    sql.append("SELECT * FROM SOME_TABLE");
    String conj = " WHERE ";
    for ( String key : filter.keySet() ) {
        sql.append(conj).append(key).append(" = :").append(key);
        conj = " AND ";
    }
    sql.append(" ORDER BY SOME_FIELD DESC");
    if ( start >= 0 ) {
        sql.append(" OFFSET :start");
        filter.put("start", ""+start);  // Needs to be in map for binding
    }
    if ( limit >= 0 ) {
        sql.append(" LIMIT :limit");
        filter.put("limit", ""+limit); // Needs to be in map for binding
    }
    return sql.toString();
}
© www.soinside.com 2019 - 2024. All rights reserved.