【问题标题】:Checking FormRequest's validation rules before authorization in Laravel在 Laravel 中授权之前检查 FormRequest 的验证规则
【发布时间】:2019-08-01 21:51:27
【问题描述】:

我正在使用 JSON:API 规范在 Laravel 中实现一个 API。

在其中我有一个资源,我们称之为 Ponds,与另一个资源具有多对多关系,我们称之为 Ducks。

根据 JSON:API 规范,为了删除这种关系,我应该使用 DELETE /ponds/{id}/relationships/ducks 端点,并请求以下正文:

{
    "data": [
        { "type": "ducks", "id": "123" },
        { "type": "ducks", "id": "987" }
    ]
}

这是由 PondRemoveDucksRequest 处理的,如下所示:

<?php
...
class PondRemoveDucksRequest extends FormRequest
{
    public function authorize() 
    {
        return $this->allDucksAreRemovableByUser();
    }

    public function rules()
    {
        return [
            "data.*.type" => "required|in:ducks",
            "data.*.id" => "required|string|min:1"
        ];
    }

    protected function allDucksAreRemovableByUser(): bool
    {
        // Here goes the somewhat complex logic determining if the user is authorized 
        // to remove each and every relationship passed in the data array.
    }
}

问题是,如果我发送一个正文,例如:

{
    "data": [
        { "type": "ducks", "id": "123" },
        { "type": "ducks" }
    ]
}

,我得到一个 500,因为首先触发了授权检查,并且它依赖于数组中每个项目中存在的 id。理想情况下,我希望收到来自规则验证的标准消息的 422 错误。

我看到的快速解决方法是在 allDucksAreRemovableByUser() 方法中添加 id 存在检查,但这似乎有点 hacky。

有没有更好的方法先检查验证规则,然后才进行授权部分?

提前致谢!

【问题讨论】:

  • 请问您可以在哪里发布您调用验证规则的位置吗?
  • @party-ring 我不确定我是否得到你的问题。据我了解,在控制器方法的参数中使用请求时会自动检查验证规则。
  • 在您的控制器中,当您检索请求时,您能否 dd $request-&gt;validated(); 只是为了查看请求是否通过验证?因为如果它通过了,那么验证就有问题,如果它失败了,那么你需要拒绝那个请求:)
  • @party-ring 规则验证本身似乎有效,因为如果在已删除的项目之一中提供了错误的“类型”,它会返回 422。我会检查是否添加了 $request->validated();不过,有助于规则顺序与授权检查,谢谢!
  • @party-ring 在控制器中添加$request-&gt;validated() 调用没有任何区别。我假设 FormRequest 的授权逻辑在控制器方法中的任何代码之前执行。有帮助的是在authorize() 方法的开头添加$this-&gt;getValidatorInstance()-&gt;validated();getValidatorInstance() 是必需的,因为在执行授权逻辑时,验证器尚未实例化。这样我就得到了基于rules() 的标准错误消息和验证。不过看起来还是有点乱,我正在考虑将其移至中间件。

标签: php laravel json-api


【解决方案1】:

这里的方法与您尝试的方法略有不同,但它可能会为您实现预期的结果。

如果您尝试验证给定的鸭子 id 是否属于用户,可以在规则本身中执行以下操作:

"data.*.id" => "exists:ducks,id,user_id,".Auth::user()->id

这条规则询问是否有一条记录存在于ducks 表中,它与id 匹配,并且user_id 是当前登录的user_id。

如果您将其链接到现有规则 (required|string|min:1),使用“保释”,那么除非它首先通过其他三个规则,否则它不会运行查询:

"data.*.id" => "bail|required|string|min:1|exists:ducks,id,user_id,".Auth::user()->id

【讨论】:

  • 如果授权逻辑仅依赖于用户和池塘之间的关系,这看起来是一个有效的解决方案。不幸的是,在我的情况下,还有更多内容 - 需要确定用户的特权、与池塘父实体的关系(目前找不到一个很好的类比;))等等。无论如何,谢谢,我认为这可以在许多其他情况下会有所帮助。
【解决方案2】:

authorize()方法的开头添加$this-&gt;getValidatorInstance()-&gt;validate();

【讨论】:

    【解决方案3】:

    1 - 在 App\Requests 目录中创建名为“FormRequest”的抽象类并覆盖 validateResolved() 方法:

    
    <?php
    
    namespace App\Http\Requests;
    
    use Illuminate\Validation\ValidationException;
    use Illuminate\Auth\Access\AuthorizationException;
    use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
    
    abstract class FormRequest extends BaseFormRequest
    {
    
        /**
         * Validate the class instance.
         *
         * @return void
         * @throws AuthorizationException
         * @throws ValidationException
         */
        public function validateResolved()
        {
    
            $validator = $this->getValidatorInstance();
    
            if ($validator->fails())
            {
                $this->failedValidation($validator);
            }
    
            if (!$this->passesAuthorization())
            {
                $this->failedAuthorization();
            }
        }
    
    }
    
    

    2 - 使用自定义 FormRequest 扩展您的 FormRequests

    <?php
    
    namespace App\Http\Requests\Orders;
    
    use App\Http\Requests\FormRequest;
    
    class StoreOrderRequest extends FormRequest
    {
    
    
    
    }
    
    
    

    【讨论】:

      【解决方案4】:

      我发现解决这个问题的最干净的解决方案是为FormRequest 创建一个小特征,并在您想要在授权之前运行验证的任何时候使用它,请查看以下示例:

      <?php
      
      namespace App\Http\Requests\Traits;
      
      /**
       *  This trait to run the authorize after a valid validation
       */
      trait AuthorizesAfterValidation
      {
          /**
           * Determine if the user is authorized to make this request.
           *
           * @return bool
           */
          public function authorize()
          {
              return true;
          }
      
          /**
           *  Set the logic after the validation
           * 
           * @param $validator
           * @return void
           */
          public function withValidator($validator)
          {
              $validator->after(function ($validator) {
                  if (! $validator->failed() && ! $this->authorizeValidated()) {
                      $this->failedAuthorization();
                  }
              });
          }
      
          /**
           *  Define the abstract method to run the logic.
           * 
           * @return void
           */
          abstract public function authorizeValidated();
      }
      

      然后在你的请求类中:

      <?php
      
      namespace App\Http\Requests;
      
      use Illuminate\Foundation\Http\FormRequest;
      use App\Http\Requests\Traits\AuthorizesAfterValidation;
      
      class SomeKindOfRequest extends FormRequest
      {
          use AuthorizesAfterValidation;
      
          /**
           * Determine if the user is authorized to make this request.
           *
           * @return bool
           */
          public function authorizeValidated()
          {
              return true; // <---- Set your authorization logic here
          }
      
          /**
           * Get the validation rules that apply to the request.
           *
           * @return array
           */
          public function rules()
          {
              return [
                  //
              ];
          }
      }
      

      来源https://github.com/laravel/framework/issues/27808#issuecomment-470394076

      【讨论】:

        猜你喜欢
        • 2020-06-25
        • 2020-01-05
        • 2023-03-12
        • 2019-05-05
        • 2014-05-20
        • 1970-01-01
        • 2015-04-30
        • 2020-06-28
        • 2017-08-27
        相关资源
        最近更新 更多