【问题标题】:How to best validate JSON on the server-side如何在服务器端最好地验证 JSON
【发布时间】:2015-07-21 07:01:24
【问题描述】:

在服务器端处理 POST、PUT 和 PATCH 请求时,我们经常需要处理一些 JSON 来执行请求。

很明显,我们需要以某种方式验证这些 JSON(例如结构、允许/预期的键和值类型),我可以看到至少两种方式:

  1. 收到 JSON 后,预先验证 JSON 的原样对其执行任何操作以完成请求。

  2. 按原样获取 JSON,开始处理它(例如访问它的各种键值)并尝试在执行业务逻辑时随时验证它,并可能使用一些异常处理来处理时尚数据。

与第二种方法相比,第一种方法似乎更健壮,但可能更昂贵(时间成本),因为每个请求都会被验证(希望它们中的大多数都是有效的,所以验证有点多余)。

第二种方法可以省去对有效请求的强制验证,但是在业务逻辑中混合检查可能有问题甚至有风险。

以上两个哪个更好?或者,还有更好的方法吗?

【问题讨论】:

  • 第一种方法对于未来的重构也更加健壮。
  • 我建议分开你的验证。在预先验证时,仅验证基本(数据类型、空值、大小)标准。在业务层,随时随地验证业务的正确性(例如,提供的代码是否正确)。将业务层的验证代码模块化,我认为这不会有问题/风险。
  • 你使用的是什么网络框架?
  • @Sdra,龙卷风 (python)
  • 感谢您的回答。我对每个人都投了赞成票,因为我非常感谢您的意见。

标签: json web-services validation api security


【解决方案1】:

这真的取决于您的要求。但总的来说,我总是选择#1

几个注意事项

为了保持一致性,我会使用 #1 方法,以提高性能 #2。但是,当使用 #2 时,您必须考虑到,随着逻辑的变化,将来在输入无效的情况下回滚可能会变得复杂。

Json 验证不应该花那么长时间。在 python 中,您可以使用 ujson 来解析 json 字符串,这是 json python 模块的超快 C 实现

对于验证,我使用 jsonschema python 模块,它使 json 验证变得容易。

另一种方法

如果使用jsonschema,可以分步验证json请求。我将对 json 结构中最常见/最重要的部分执行初始验证,并沿业务逻辑路径验证其余部分。这将允许编写更简单的 json 模式,因此更轻量级。

最终决定

如果(且仅当)这个决定很关键,我会实施这两种解决方案,在正确和错误的输入条件下对它们进行时间分析,并根据错误的输入频率对结果进行加权。因此:

  • 1c = 方法 1 在c正确输入
  • 上花费的平均时间
  • 1w = 方法 1 在w错误输入
  • 上花费的平均时间
  • 2c = 方法 2 在c正确输入
  • 上花费的平均时间
  • 2w = 方法 2 在w错误输入
  • 上花费的平均时间
  • CR = c修正输入rate(或频率)
  • WR = wrong 输入 rate(或频率)

    if ( 1c * CR ) + ( 1w * WR) <= ( 2c * CR ) + ( 2w * WR):
        chose method 1
    else:
        chose method 2
    

【讨论】:

    【解决方案2】:

    答案完全取决于您的用例。

    如果您希望所有调用都源自受信任的客户端,则应实施前期架构验证,以便仅在您设置调试标志时激活它。

    但是,如果您的服务器提供公共 api 服务,那么您应该预先验证调用。这不仅仅是性能问题 - 您的客户、黑客、竞争对手等可能会仔细检查您的服务器是否存在安全漏洞。

    如果您的服务器向不受信任的客户端提供私有 api 服务(例如,在封闭的网络设置中,它必须与来自 3rd 方开发人员的系统集成),那么您至少应该预先运行这些检查,以免您因别人的错误而受到指责。

    【讨论】:

      【解决方案3】:

      我肯定会在处理之前进行验证。

      假设您收到一些 json 数据,其中包含您期望的 10 个变量:

      • 前 5 个变量的类型为 string
      • 6 和 7 应该是 整数
      • 8、9 和 10 应该是 数组

      您可以在开始处理任何此类数据之前进行快速变量类型验证,如果十个中的一个失败,则返回验证错误响应。

      foreach($data as $varName => $varValue){
          $varType = gettype($varValue);
          if(!$this->isTypeValid($varName, $varType)){
              // return validation error
          }
      }
      
      // continue processing
      

      想想你直接处理数据然后第 10 个值是无效类型的场景。处理前 9 个变量是一种资源浪费,因为无论如何您最终都会返回一些验证错误响应。最重要的是,您必须回滚已保存到存储中的所有更改。

      我在示例中仅使用变量类型,但我建议在处理任何变量之前对所有变量进行全面验证(长度、最大值/最小值等)。

      【讨论】:

        【解决方案4】:

        您使用 POST、PUT 和 PATCH 描述的内容听起来像是在实现 REST API。根据您的后端平台,您可以使用将 JSON 映射到非常强大的对象并为您执行验证的库。在 JAVA 中,您可以使用JerseySpringJackson。如果您使用的是 .NET,则可以使用Json.NET

        如果您的目标是效率并且您想要验证每个请求,那么如果您可以在前端进行评估,如果您使用的是 JavaScript,那么您可以使用 json2.js,这将是理想的选择。

        关于比较您的方法,这里有一个优点/缺点列表。

        方法 #1:根据要求

        优点

        1. 业务逻辑完整性得到维护。正如您所提到的,在处理业务逻辑时尝试验证可能会导致无效测试,而实际上可能是有效的,反之亦然,或者验证可能会无意中对业务逻辑产生负面影响。
        2. 正如 Norbert 所说,提前发现错误将提高效率。这提出的逻辑问题是,如果一开始就有错误,为什么还要花时间处理?
        3. 代码将更简洁,更易于阅读。将验证和业务逻辑分开将使代码更清晰、更易于阅读和维护。

        缺点

        1. 这可能会导致冗余处理,这意味着更长的计算时间。

        方法 #2:随时随地验证

        优点

        1. 理论上,通过同时执行它们节省过程和计算时间,它是有效的。

        缺点

        1. 实际上,节省的处理时间可能可以忽略不计(如 Norbert 所述)。无论哪种方式,您仍在进行验证检查。此外,如果发现错误,则会浪费处理时间。
        2. 可以包含数据完整性。以这种方式处理 JSON 时,它可能会损坏。
        3. 代码不是很清楚。在阅读业务逻辑时,由于混入了验证逻辑,发生的情况可能并不明显。

        真正归结为准确度 vs 速度。它们通常具有反比关系。随着您变得更加准确并验证您的 JSON,您可能不得不在速度上做出一些妥协。这实际上仅在大型数据集中才引人注目,因为如今计算机的速度非常快。鉴于您认为接收数据时数据的准确性,或者额外的一秒左右是否至关重要,由您决定什么更重要。在某些情况下,它确实很重要(即对于股票市场和医疗保健应用,毫秒很重要)并且两者都非常重要。正是在这些情况下,当您增加一个(例如准确性)时,您可能必须通过获得更高性能的机器来提高速度。

        希望这会有所帮助。

        【讨论】:

          【解决方案5】:

          一般来说,第一个选择是要走的路。您可能需要考虑第二个选项的唯一原因是,如果您正在处理数十 MB 或更大的 JSON 数据。

          换句话说,只有当您尝试流式传输 JSON 并动态处理它时,您才需要考虑第二种选择。

          假设每个 JSON 最多处理几百 KB,您可以选择选项一。

          您可以按照以下步骤操作:

          1. 使用像 GSON 这样的 JSON 解析器,它只会转换你的整个 JSON 输入到相应的 Java 域模型对象中。 (如果 GSON 不抛出异常,你可以确定 JSON 是 完全有效。)
          2. 当然,在步骤 1 中使用 GSON 构造的对象 可能不在功能上有效的状态。例如,函数式 必须进行必填字段和限制检查等检查。
          3. 为此,您可以定义一个重复的 validateState 方法 验证对象本身及其子对象的状态。

          这是validateState 方法的示例:

          public void validateState(){ 
              //Assume this validateState is part of Customer class.
          
              if(age<12 || age>150) 
                  throw new IllegalArgumentException("Age should be in the range 12 to 120");
              if(age<18 && (guardianId==null || guardianId.trim().equals("")) 
                  throw new IllegalArgumentException("Guardian id is mandatory for minors");
          
              for(Account a:customer.getAccounts()){
                  a.validateState(); //Throws appropriate exceptions if any inconsistency in state
              }
          }
          

          【讨论】:

            【解决方案6】:

            第一种方法更健壮,但不必明显更昂贵。即使您能够由于错误而中止解析过程,它也会变得更便宜:您的业务逻辑通常会占用流程中> 90%的资源,因此如果您的错误百分比为 10%,那么您已经是资源中立的.如果您优化验证流程以便预先执行来自业务流程的验证,您的错误率可能会低得多(例如 20 分之一到 100 分之一)以保持资源中立。

            有关假设预先数据验证的实施示例,请查看 GSON (https://code.google.com/p/google-gson/):

            GSON 的工作原理如下:JSON 的每一部分都可以转换为一个对象。此对象是类型化的或包含类型化的数据: 示例对象(使用 JAVA 作为示例语言):

            public class someInnerDataFromJSON {
                String name;
                String address;
                int housenumber;
                String buildingType;
                // Getters and setters
                public String getName() { return name; }
                public void setName(String name) { this.name=name; }
                //etc.
            }
            

            GSON 解析的数据是使用提供的模型,已经过类型检查。 这是您的代码可以中止的第一个点。

            假设数据已被模型确认,在此退出点之后,您可以验证数据是否在一定范围内。您也可以将其写入模型中。

            假设这个 buildingType 是一个列表:

            • 独户住宅
            • 多户住宅
            • 公寓

            您可以在解析期间通过创建一个检查数据的 setter 来检查数据,或者您可以在您的业务规则应用程序的第一组解析后检查它。先检查数据的好处是你后面的代码会少处理异常,所以代码更少更容易理解。

            【讨论】:

              猜你喜欢
              • 2012-01-14
              • 2012-05-04
              • 2011-11-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多