【问题标题】:Laravel user capabilitiesLaravel 用户能力
【发布时间】:2016-02-22 22:17:02
【问题描述】:

在 Laravel 中,您可以轻松地 define abilities,然后在用户请求执行不同操作时连接到它们:

$gate->define('update-post', function ($user, $post) {
    return $user->id === $post->user_id;
});

但我定义的几乎所有能力都有这部分$user->id === $model->user_id。我不喜欢它,因为它是一种一遍又一遍地重复我认为可能更抽象的条件。

我定义的大部分能力都是根据更新/删除记录,所以如果我可以将全局条件应用于所有这些会更好,或者如果可以有一个组能力来定义类似于我们所做的事情路由。

有什么解决方法吗?我真的很喜欢它干。

【问题讨论】:

    标签: php laravel laravel-5.1


    【解决方案1】:

    Laravel 中的一切都是可扩展的,这就是它的服务提供者的力量。

    您可以将Gate 对象扩展为MyCustomGate 对象,并在该对象中执行您想要的任何操作。这是一个例子:

    MyCustomGate.php

    class MyCustomGate extends \Illuminate\Auth\Access\Gate
    {
        protected $hasOwnershipVerification = [];
    
        /**
         * Define a new ability.
         *
         * @param  string  $ability
         * @param  callable|string  $callback
         * @return $this
         *
         * @throws \InvalidArgumentException
         */
        public function defineWithOwnership($ability, $callback, $foreignUserIdKey = "user_id")
        {
            // We will add this 
            $this->hasOwnershipVerification[$ability] = $foreignUserIdKey;
    
            return $this->define($ability, $callback);
        }
    
        /**
         * Resolve and call the appropriate authorization callback.
         *
         * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
         * @param  string  $ability
         * @param  array  $arguments
         * @return bool
         */
        protected function callAuthCallback($user, $ability, array $arguments)
        {
            $callback = $this->resolveAuthCallback(
                $user, $ability, $arguments
            );
    
            // We will assume that the model is ALWAYS the first key
            $model = is_array($arguments) ? $arguments[0] : $arguments;
    
            return $this->checkDirectOwnership($ability, $user, $model) && call_user_func_array(
                $callback, array_merge([$user], $arguments)
            );
        }
    
        /**
         * Check if the user owns a model.
         *
         * @param  string  $ability
         * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
         * @param  \Illuminate\Database\Eloquent\Model  $model
         * @return bool
         */
        protected function checkDirectOwnership($ability, $user, $model)
        {
            if(!isset($this->hasOwnershipVerification[$ability])) {
                return true
            }
    
            $userIdKey = $this->hasOwnershipVerification[$ability];
    
            // getAuthIdentifier() is just ->id, but it's better in case the pk of a user is different that id
            return $user->getAuthIdentifier() == $model->{$userIdKey};
        }
    }
    

    然后,你必须告诉 Laravel 使用你的门而不是默认门。您可以在 AuthServiceProvider 中执行此操作(假设它正在扩展 Illuminate\Auth\AuthServiceProvider,只需添加以下方法即可。

    AuthServiceProvider

    /**
     * Register the access gate service.
     *
     * @return void
     */
    protected function registerAccessGate()
    {
        $this->app->singleton(\Illuminate\Contracts\Auth\Access\Gate::class, function ($app) {
            return new MyCustomGate($app, function () use ($app) {
                return $app['auth']->user();
            });
        });
    }
    

    这样,您可以使用defineWithOwnership() 方法而不是define() 来定义能力。对于不需要所有权验证的功能,您仍然可以使用 define()。还有第三个参数defineWithOwnership() 接受,它是$foreignUserIdKey;这用于模型具有不同的用户 ID 字段的情况。

    注意:我是即时编写的代码,没有尝试,可能有错误,但你明白了。

    【讨论】:

    • 谢谢蓝。这是一个不错且干净的方法,但我现在能从中得到的只是一个错误Unresolvable dependency resolving [Parameter #1 [ <required> callable $userResolver ]] in class Illuminate\Auth\Access\Gate
    • 我不确定,但我认为自定义覆盖的 registerAccessGate() 方法没有被调用。
    • 好吧,你的 AuthServiceProvider 是不是扩展了 Laravel 的 AuthServiceProvider?您是否使用自定义 register() 方法?另一种注册方法是将函数重命名为其他名称,然后在您的 register() 方法中的 parent::register() 之后调用该函数。
    • 一个完整的异常堆栈会很有用(省略任何敏感信息)。您可以使用粘贴箱并将其链接到此处。
    • 我的AuthServiceProvider 类默认扩展Illuminate\Foundation\Support\Providers\AuthServiceProvider。我没有在我的AuthServiceProvider 中使用自定义的register() 方法,但正如您提供的那样,我写了一个public register(); 并调用了parent::register()registerAccessGate() 方法的重命名形式。然而,我收到了与上面评论完全相同的错误。我粘贴了相关的 laravel.log 部分 here.
    【解决方案2】:

    我仔细检查了你的问题,但没有找到“简单”的方法。

    相反,我可能会这样做:

    <?php
    
    
    namespace App\Policies;
    
     use App\User;
     use App\Post;
    
    trait CheckOwnership {
        protected function checkOwnership($user, $model) {
            $owned = $user->id === $model->user_id;
            if ($owned === false)
                 throw new NotOwnedException;
        }    
     }
    
     class PostPolicy
     {
    
         use CheckOwnership;
    
        public function update(User $user, Post $post)
        {
             try {
                 $this->checkOwnership($user, $post);
                 //continue other checks
             } catch (NotOwnedException $ex) {
                 return false;
             } 
        }
     }
    

    【讨论】:

      【解决方案3】:

      将此功能添加到您的 AuthServiceProvider

          public function defineAbilities(array $abilities, $gate)
          {
              foreach($abilities as $name => $model){
                  $gate->define($name, function ($user, $model){
                      return $user->id === ${$model}->user_id;
                  });
              }
          }
      

      然后在boot方法里面

      $this->defineAbilities(['ability1' => 'model1', 'ability2' => 'model2'], $gate);
      

      【讨论】:

      • 谢谢,但我没有说我的能力只返回一个 $user-&gt;id === $model-&gt;user_id 结果。我说它这部分,所以它可能像$user-&gt;id == $post-&gt;user_id &amp;&amp; $post-&gt;status == 0
      【解决方案4】:

      您可以定义另一个函数并在匿名函数中调用它。这将允许您在一个中心位置拥有常用代码,同时仍然允许任何特定于资源的逻辑。

      将此函数添加到您的AuthServiceProvider 类中:

      public function userCheck(User $user, $target)
      {
          // do the user id check
          $result = isset($target->user_id) && isset($user) && $user->id === $target->user_id;
          return $result;
      }
      

      您的代码,已修改:

      $gate->define('update-post', function ($user, $post) {
          // call the function
          $result = $this->userCheck($user, $post);
          // do some kind of 'update-post' specific check
          return $result/* && some_bool_statement*/;
      });
      

      【讨论】:

      • Sliphon 我遇到了一些奇怪的事情:define 的第二个参数只能是 callable 类型。有什么解决方法吗?
      • Siphon 似乎$user$post 作为userCheck() 的第一个和第二个参数没有传递给函数:Undefined variable: user
      • 如果您可以修改您的答案以获得有效的解决方案,那么我会毫不犹豫地再奖励一次。
      • 我已将答案更新为更优雅的内容。似乎闭包会使代码变得不必要地复杂。您需要做的事情可以通过一个简单的辅助函数来整合逻辑轻松完成。
      【解决方案5】:

      我认为你可以使用中间件。

      只需制作一个管理中间件并在您的路由和路由组中使用它。

      因为 Laravel 有 csrf 令牌,所以您的项目没有安全漏洞(删除、创建和...操作)!

      你也可以使用before()函数。

      然后是一个重要提示:

      如果您没有在 Policy 类上定义相应的函数并在控制器上将其称为 $this-&gt;authorize($post),则会引发 unauthorized Action 错误,除非 before()methodreturnstrue

      例如在Dashboard\PostsController 上调用$this-&gt;authorize

      public function edit($id)
      {
          $post = Post::find($id)->first();
          $this->authorize($post);
          return view('dashboard.post')->with(compact('post'));
      }
      

      如果我们定义了一个 PostPolicy 类:

      class PostPolicy
      {
          use HandlesAuthorization;
      
          public function before($user, $ability)
          {
              return $user->is_admin;
          }
      }
      

      如果用户是管理员,他/她可以编辑帖子,因为我们在 before() 方法中 returned true 尽管没有同名的方法(如 PostsController 中的 edit 方法)。

      事实上,Laravel 会检查 Policy Class 上的 before 方法 mthod。如果before return'snull 将在控制器方法上检查具有相同名称的对应方法,如果找不到此方法,则用户无法执行操作。

      感谢 laravel 干我们!♥

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-09-09
        • 1970-01-01
        • 2018-03-16
        • 1970-01-01
        • 2016-06-20
        • 2012-11-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多