是直接执行SQL坏应用程序设计?

时间:2011-01-07 20:54:44

标签: sql stored-procedures ios

我正在开发一个iOS应用程序,它是另一个项目的经理/查看器。这个想法是应用程序将能够将存储在数据库中的数据处理成多个可视化 - 整体效果类似于cacti。我正在使可视化完全由用户配置:用户定义她想要查看的内容并添加限制。

例如,她可能会指定在过去三周内使用当前处于活动状态且不在美国的用户帐户来绘制指标。

我的问题是,我能想到的唯一设计是或多或少地将直接SQL从iOS应用程序传递到后端服务器以对数据库执行。我知道这是不好的做法,一切都应该根据存储过程来编写。但是,我如何保持足够的灵活性以保持完全用户定义的查询?

虽然应用程序确实组成了SQL,但用户永远不会看到或注入直接SQL。这些都是在UIDateTimeChoosers,UIPickerViews等中抽象出来的。

5 个答案:

答案 0 :(得分:3)

编辑:我来这里不喜欢我的回答。我同意下面的一些评论者,我建议您在客户端上构建“查询”对象,并将它们传递给使用预准备语句构造SQL语句的Web服务。这样可以安全地使用SQL注入,因为您正在使用预准备语句,并且可以控制您控制的Web服务中构造内容的安全性。

编辑结束

执行从客户端传递的SQL没有任何问题。特别是在查询构建情况下。

例如,您可以通过将其与“AND”连接来添加任意数量的where子句。但是,您不应该做的是允许用户指定SQL是什么。您应该提供允许用户构建查询的界面。有几个原因这是有利的:

  1. 更好的用户体验(谁想要编写除开发人员以外的SQL?)
  2. 注射更安全。你无法过滤掉所有危险的SQL字符串。
  3. 除此之外,执行动态SQL而不是使用存储过程绝对没问题。您认为everything should be written in terms of stored procedures似乎误入歧途的观点。当然,存储过程在很多方面都很好,但使用它们也有许多缺点。

    事实上,过度使用存储过程有时会导致性能问题,因为开发人员在多个位置重用相同的存储过程,即使他们不需要返回所有数据。

    您可能想要研究的一件事是在服务器端构建SQL并传递构建查询的某种内部表示。如果您有某种暴露的Web服务并允许您的客户端运行它想要运行的任何SQL,那么您就会遇到安全问题。这也有助于版本控制。如果修改数据库,则可以使用它修改Web服务,而不必担心使用旧客户端构建无效SQL的人。

答案 1 :(得分:3)

数据库中的所有数据是否可供所有用户使用,或者您是否只允许每个用户访问数据的子集?如果是后者,只需将数据库登录限制为只读访问权限就不足以保护您的数据。

作为一个简单的例子,用户可能会破坏您的界面以提交查询SELECT password, salt FROM users WHERE login = 'admin',劫持响应以获取原始数据,并强制管理您的管理员密码。随着应用程序的普及,恶意用户群的增长超过了线性,直到最终他们的集体智慧超过了你的团队;你不应该把自己置于成功将成为你垮台的境地。

您可以接受客户端应用程序发送的SQL查询,并尝试在服务器端解析它,以便对查询应用适当的限制,以便屏蔽用户,可以这么说。但到那里需要你在服务器代码中编写一个迷你SQL解析器,谁想要做所有这些工作呢?编写可以编写SQL的代码要比编写可以读取它的代码容易得多。

我的团队在一个相当复杂的Web应用程序中解决了报告界面的类似问题,我们的方法是这样的:

由于您已经打算使用图形界面来构建查询,因此将原始数据从界面元素转换为表示用户输入(以及查询)的数据结构相当容易。例如,用户可以使用您的界面指定他们希望将结果限制在2010年5月5日除John之外的所有人收集的条件。 (假设John的UserID是3.)使用我的团队使用的JSON格式的变体,您只需将UI中的数据转换为以下内容:

{ "ConditionType": "AND",
  "Clauses": [
    { "Operator": "Equals",
      "Operands": [
        { "Column": "CollectedDate" },
        { "Value": "2010-05-05" }
      ]
    },
    { "Operator": "NotEquals",
      "Operands": [
        { "Column": "CollectedByUserID" },
        { "Value": 3 }
      ]
    }
  ]
}

在客户端,创建这种数据结构与创建SQL查询的任务非常同构,并且可能更容易一些,因为您不必担心SQL语法。

这里有一些细微之处,我正在掩饰。这仅代表查询的WHERE部分,并且必须存在于更大的对象({ "Select": ..., "From": ..., "Where": ..., "OrderBy": ... })中。更复杂的情况也是可能的。例如,如果您要求用户能够指定多个表以及它们如何一起加入,则在将列指定为WHERE子句中的操作数时,必须更具体。但同样,所有这些都是您无论如何都要直接构建查询的工作。

然后,服务器将反序列化此结构。 (值得指出的是,用户提供的列名不应该被弄脏 - 我们将它们映射到应用程序中允许列的列表;如果列不在列表中,则反序列化失败并且用户得到了错误消息。)使用简单的对象结构,对查询进行更改几乎是微不足道的。服务器应用程序可以修改WHERE子句列表以应用适当的数据访问限制。例如,您可能会说(在伪代码中)Query.WhereClauses.Add(new WhereClause(Operator: 'Equals', Operands: { 'User.UserID', LoggedInUser.UserID } ))

然后,服务器代码将对象传递给一个相对简单的查询构建器,该构建器遍历对象并拆分SQL查询字符串。这比听起来容易,但要确保所有用户提供的参数都干净利落地传递。不要清理 - 使用参数化查询。

这种方法最终对我们来说非常好,原因如下:

  1. 它让我们打破了从图形界面编写查询的复杂性。
  2. 确保用户生成的查询从未执行过脏。
  3. 它使我们能够为各种访问限制的查询添加任意子句。
  4. 它足够可扩展,我们可以做一些漂亮的事情,比如允许用户搜索自定义字段。
  5. 从表面上看,它看起来似乎是一个复杂的解决方案,但我的团队发现其好处很多,而且实施起来很干净且可维护。

答案 2 :(得分:1)

我认为这完全是用户可配置的可视化更像是构建块。 我不会将直接的sql查询传递给后端。我会让用户发送参数(使用哪个视图,在where子句中过滤,依此类推)。但是让用户注入sql是一个潜在的噩梦(无论是安全还是维护)

答案 3 :(得分:0)

如果您想让用户通过实际的sql发送,请尝试过滤“drop and truncate”等字词。如果必须允许删除,则可以强制它们使用主键。

答案 4 :(得分:0)

将SQL命令发送到数据库的应用程序没有任何问题,只要您了解注入问题即可。所以不要在你的代码中执行此操作:

(Pseudocode)

String sqlCommand = "SELECT something FROM YOURTABLE WHERE A='" + aTextInputFieldInYourGui + "'";
cmd.execute(sqlCommand);

为什么不呢?看看如果用户将此行输入aTextInputFieldInYourGui中会发生什么 'GO DELETE * from YOURTABLE GO SELECT'
(假设您的数据库是MS SQL Server,对于其他RDBMS略有不同的语法)

使用预准备语句和参数绑定

(Pseudocode)
String sqlCommand = "SELECT something FROM YOURTABLE WHERE A=?";
cmd.prepare(sqlCommand);
cmd.bindParam(1, aTextInputFieldInYourGui);
cmd.execute();

此致