用户授权,策略的使用

Laravel框架
393
0
0
2022-09-04
标签   Laravel授权

使用场景

限制当前用户操作。如:当前用户只能操作自己的数据。

示例

用户仅能编辑,删除,自己发布的文章。

文章简化表结构articles如下:

articles 
    id - integer 
    title - string 
    content - text 
    user_id - integer

策略

策略是围绕特定模型或资源组织授权逻辑的类。

生成策略

生成文章操作限制策略类:

你可以使用 make:policy Artisan 命令生成策略。生成的策略将放置在 app/Policies 目录中。如果应用程序中不存在此目录,Laravel 将自动创建。

生成空的授权策略类:

php artisan make:policy ArticlePolicy

生成一个包含与查看、创建、更新和删除资源相关的示例策略方法的类,可以在执行命令时提供一个 --model 选项:

php artisan make:policy ArticlePolicy --model=Article

手动注册策略发现

注册策略是告知 Laravel 在授权针对给定模型类型的操作时使用哪个策略。

Laravel 应用程序中包含的 App\Providers\AuthServiceProvider 包含一个 policies 属性,它将 Eloquent 模型映射到其相应的策略。 注册策略将指示 Laravel 在授权针对给定 Eloquent 模型的操作时使用哪个策略:

<?php

namespace App\Providers;

use App\Models\Article;
use App\Policies\ArticlePolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * 应用程序的策略映射。
     */ 
    protected $policies = [
        Article::class => ArticlePolicy::class, // 指定Article模型使用ArticlePolicy::class策略类
    ];

    /**
     * 注册任何应用程序身份验证/授权服务。
     */ 
    public function boot()
    {
        $this->registerPolicies();
    }
}

策略自动发现

只要模型和策略遵循标准的 Laravel 命名约定,Laravel 就可以自动发现策略,而不是手动注册模型策略。遵守约定,指定模型注册指定模型策略。

具体约定示例:模型可以放置在 app/Models目录中,而策略可以放置在 app/Policies目录中。在这种情况下,Laravel 将检查app/Policies中的策略。此外,策略名称必须与模型名称匹配并具有Policy后缀。 因此,User 模型将对应于 UserPolicy 策略类。

自定义策略发现

可以使用 Gate::guessPolicyNamesUsing 方法注册自定义策略发现回调。通常,应该从应用程序的 AuthServiceProviderboot 方法调用此方法:

<?php

namespace App\Providers;

use App\Models\Article;
use App\Policies\ArticlePolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * 应用程序的策略映射。
     *
     * @var array
     */ 
    protected $policies = [
        Article::class => ArticlePolicy::class, // 指定Article模型使用ArticlePolicy::class策略类
    ];

    /**
     * 注册任何应用程序身份验证/授权服务。
     */ 
    public function boot()
    {
        $this->registerPolicies();
        //自定义策略发现  
        Gate::guessPolicyNamesUsing(function ($modelClass) {
            //指定该模型使用的策略类。如: 'App\Models\Article' => 'App\Policies\ArticlePolicy',  
            return 'App\Policies\\'.class_basename($modelClass).'Policy';
        });
    }
}

编写策略

<?php

namespace App\Policies;

use App\Models\Article;
use App\Models\User;

class ArticlePolicy
{

    /**
     * 用户是否可以查看文章
     * @param \App\Models\User  $user
     * @return mixed
     */ 
    public function view(User $user)
    {
      //限制
    }

    /**
     * 确定用户是否可以删除给定的文章
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Article  $article
     * @return bool
     */ 
    public function delete(User $user, Article $article)
    {
        return $user->id === $article->user_id;
    }
}

策略响应

自定义响应信息,可以从你的策略方法返回一个 Illuminate\Auth\Access\Response 实例
use App\Models\Article;
use App\Models\User;
use Illuminate\Auth\Access\Response;

/**
 * Determine if the given article can be delete by the user.
 *
 * @param  \App\Models\User  $user
 * @param  \App\Models\Article  $$article
 * @return \Illuminate\Auth\Access\Response;
 */
public function delete(User $user, Article $article)
{
    return $user->id === $article->user_id
                ? Response::allow()
                : Response::deny('You do not own this article.');
}

策略过滤器

对于某些用户,您可能希望给他授权给定策略中的所有操作。为了实现这一点,你可以在策略上定义一个 before 方法。

before 方法将在策略上的所有方法之前执行,这样就使您有机会在实际调用预期的策略方法之前就已经授权了操作。该功能常用于授权应用程序管理员来执行任何操作:

use App\Models\User;

/**
 * 执行预先授权检查
 *
 * @param  \App\Models\User  $user
 * @param  string  $ability
 * @return mixed
 */
public function before(User $user, $ability)
{
    if ($user->isAdministrator()) {
        return true;
    }
}

如果你想拒绝特定类型用户的所有授权检查,那么你可以从 before 方法返回 false 。如果返回 null ,则授权检查将通过策略方法进行。

推荐策略类继承自定义基类,便于统一做权限限制

<?php

namespace App\Policies;

use Illuminate\Auth\Access\HandlesAuthorization;

class Policy
{
    use HandlesAuthorization;

    public function __construct()
    {
        //
    }

    /**
     * 执行预先授权检查
     *
     * @param  \App\Models\User  $user
     * @param  string  $ability
     * @return mixed
     */ 
    public function before($user, $ability)
    {
        if ($user->isAdmin()) {
            return true;
        }
    }
}
注意:如果策略类中不包含名称与被检查能力的名称相匹配的方法,则不会调用策略类的 before 方法。

使用策略授权操作

通过控制器辅助函数

authorize 方法接收您希望授权的操作名称和相关模型,如果该操作未被授权,该方法将抛出 Illuminate\Auth\Access\AuthorizationException 异常,Laravel 的异常处理程序将自动将该异常转换为一个带有 403 状态码的 HTTP 响应:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Article;
use Illuminate\Http\Request;

class ArticleController extends Controller
{
    /**
     * 删除指定的文章
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Article  $article
     * @return mixed
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */ 
    public function destory(Request $request, Article $article)
    {
        $this->authorize('delete', $article);
    }
}

不需要指定模型的操作

一些策略方法 如 create 不需要模型实例,在这些情况下,你应该给 authorize 方法传递一个类名,该类名将用来确定在授权操作时使用哪个策略:

use App\Models\Article;
use Illuminate\Http\Request;

/**
 * Create a new article.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return mixed
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function create(Request $request)
{
    $this->authorize('view', Article::class);
}

策略类提供额外的上下文

在使用策略授权操作时,可以将数组作为第二个参数传递给授权函数和辅助函数。

数组中的第一个元素用于确定应该调用哪个策略,其余的数组元素作为参数传递给策略方法,并可在作出授权决策时用于额外的上下文中。

例如,考虑下面的 ArticlePolicy 方法定义,它包含一个额外的 $category 参数:

/**
 * 确认用户是否可以更新给定的文章
 *
 * @param  \App\Models\User  $user
 * @param  \App\Models\Article  $article
 * @param  int  $category
 * @return mixed
 */
public function update(User $user, Article $article, int $category)
{
    //做相应限制
}

/**
 * 确认用户是否可以删除给定的文章
 *
 * @param  \App\Models\User  $user
 * @param  array $ids
 * @return mixed
 */
public function delete(User $user,  $ids)
{
    //做相应限制
}

当尝试确认已验证过的用户是否可以更新给定的文章时,我们可以像这样调用此策略方法:

/**
 * 更新给定的文章
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \App\Models\Article  $article
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function update(Request $request, Article $article)
{
    $this->authorize('update', [$article, $request->category]);
}

/**
 * 删除给定的文章
 *
 * @param  \Illuminate\Http\Request  $request
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function destory(Request $request)
{
    $this->authorize('delete', [Article::class, $request->ids]);
}

自定义授权异常响应

Laravel 中所有异常都是由 App\Exceptions\Handler 类处理。

use \Illuminate\Auth\Access\AuthorizationException;

/**
* Register the exception handling callbacks for the application.
*/
public function register()
{
    $this->renderable(function (AuthorizationException $e, $request) {
        return response()->json([
            'message' => '暂无权限'
        ], 403);
    });
}