laravel5.5授权系统

时间:2022-10-23 09:04:35

[toc]
背景:

帖子属于某个人(拥有属性user_id),如果这是个私密帖子,只有自己才可以看到,传统的做法是

class PostsController extends Controller
{
    public function show($id)
    {
        $post = Post::findOrFail($id);

        //只有登录的用户id和帖子的所属user_id相同才可以通过
        if (auth()->id() != $post->user_id) {
            // 403 权限错误
            abort(403, 'Sorry, not sorrry.'); 
        }
        
        return $post->title;
    }
}

以上做法问题不大,但是有没有更优雅的做法呢,今天要讲的就是laravel的用户授权,Laravel 有 2 种主要方式来实现用户授权:gates 和策略。

1. Gates

1.1 一个简单的使用Gates的例子

  1. 准备工作:

创建迁移文件

php artisan make:migration create_post_table --create=posts

文件内容

    Schema::create('posts', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id')->unsigned()->index();
        $table->string('title');
        $table->text('body');
        $table->timestamps();
    }); 

    \DB::table('posts')->insert(
        array(
            'user_id' => 1,
            'title' => '学习用户授权',
            'body' => '两种方式,Gates、策略',
        )   
    );  

开始迁移

php artisan migration

我们主要使用user表(laravel自带迁移文件)和posts表,其它创建Model等工作自行完成

  1. 注册Gates,在服务提供器中注册

服务提供器 App\Providers\AuthServiceProvider.php

/**
 * 注册任意用户认证、用户授权服务。
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Gate::define('update-post', function ($user, $post) {
        return $user->id == $post->user_id;
    });
}
  1. 授权判定,可以在控制器中进行权限的判定

控制器 app/Http/Controllers/PostManager.php

public function show($id)
{
    $post = Post::findOrFail($id);

    if (Gate::allows('update-post', $post)) {
        // 指定用户可以更新博客...
    }
    
    abort(403, 'Sorry, Permission denied'); 
}

以上就是一个简单的用户授权,是不是很简单,下面我们理解下更多用法

1.2 编写Gates

典型的做法是在 App\Providers\AuthServiceProvider 类中使用 Gate facade 定义。Gates 接受一个用户实例作为第一个参数,并且可以接受可选参数。

  1. 匿名函数

    public function boot()
    {
    $this->registerPolicies();
    
    Gate::define('update-post', function ($user, $post) {
        return $user->id == $post->user_id;
    });
    }
  2. 还可以使用Class@method风格字符串,比如控制器

    Gate::define('update-post', 'PostPolicy@update');
  3. 使用资源Gates

一次性定义多个Gates

Gate::resource('posts', 'PostPolicy');

等同于定义了下面4个功能

Gate::define('posts.view', 'PostPolicy@view');
Gate::define('posts.create', 'PostPolicy@create');
Gate::define('posts.update', 'PostPolicy@update');
Gate::define('posts.delete', 'PostPolicy@delete');

还可以传递第三个参数给resource方法,以增加功能

Gate::resource('posts', 'PostPolicy', [
    'image' => 'updateImage',
    'photo' => 'updatePhoto',
]);

1.3 授权动作

注意: 上面我们定义Gate的时候,闭包函数第一个参数为$user, 但是你并不需要传递当前认证通过的用户给这些方法。Laravel 会自动处理好传入的用户,然后传递给 gate 闭包函数

  1. 基本用法,使用Gate Facades
    ```
    //允许授权
    if (Gate::allows('update-post', $post)) {
    // 指定用户可以更新博客...
    }

//否定授权
if (Gate::denies('update-post', $post)) {
// 指定用户不能更新博客...
}


2. 不需自动处理用户,自己指定一个用户,可以使用Gate Facade的forUser()方法

if (Gate::forUser($user)->allows('update-post', $post)) {
// 指定用户可以更新博客...
}

if (Gate::forUser($user)->denies('update-post', $post)) {
// 指定用户不能更新博客...
}


# 2. policy策略

显然Gates简单易用,但是当我们需要授权的动作过多的时候,就显得比较臃肿了,管理起来麻烦,laravel提供了我们另一种方式,来实现同样的功能,就是policy

## 2.1 还是先看个例子

1. 创建策略文件

php artisan make:policy PostPolicy --model=Post


2. 编写文件

以上命令会在app\Policies\PostPolicy.php文件,该文件已经包含了4个基本的「CRUD」策略方法,我们可以增删各种方法,这里只补充view方法
public function view(User $user, Post $post)
{   
    //  
    return $user->id == $post->user_id;
}


public function create(User $user)
{   
    //  
}


public function update(User $user, Post $post)
{
    //
}


public function delete(User $user, Post $post)
{
    //
}

3. 注册策略

服务提供器 App\Providers\AuthServiceProvider.php, 更改$policies属性
protected $policies = [
    'App\Post' => 'App\Policies\PostPolicy',
];

4. 授权判定

控制器 app/Http/Controllers/PostManager.php

public function show($id)
{
$post = Post::findOrFail($id);

if ($user->can('views', $post)) {
    // 指定用户可以 ......
}

abort(403, 'Sorry, Permission denied'); 

}


以上就是使用policy的一个简单实例,下面还是进行更细致的梳理

## 2.2 编写策略

生成策略文件后,我们可以自己创建删除方法,以满足我们的需求,可以为自定义策略方法使用自己喜欢的名字
public function view(User $user, Post $post)
{   
    //  
    return $user->id == $post->user_id;
}

public function create(User $user)
{   
    //  
}

// 其它更多需要的方法

我们还可以使用策略过滤器

比如我们想要超级管理员有所有权限,可以在策略中定义一个 before 方法

public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}

如果你想拒绝用户所有的授权,你应该在 before 方法中返回 false。如果返回的是 null,则通过其它的策略方法来决定授权与否。


## 2.3 授权策略

### 2.3.1 通过用户模型

1. 指定模型的动作
Laravel 应用内置的 User 模型包含 2 个有用的方法来授权动作:can 和 cant

if ($user->can('update', $post)) {
//
}

if ($user->cant('update', $post)) {
//
}


**注意**: 如果指定模型的 策略已被注册,can 方法会自动调用核实的策略方法并且返回 boolean 值。如果没有策略注册到这个模型,can 方法会尝试调用和动作名相匹配的基于闭包的 Gate。

2. 不指定模型的动作

比较下面的两个方法
public function view(User $user, Post $post)
{   
    //  
    return $user->id == $post->user_id;
}

public function create(User $user)
{   
    //  
}
试想,如果我们有另个一个VideoPolice, 同样有view和create方法。
public function view(User $user, Video $video)
{   
    //  
    return $user->id == $video->user_id;
}

public function create(User $user)
{   
    //  
}

不难想象,尽管第一参数都是view, 第二个传入了模型实例,我们在注册策略的时候做了模型和策略的映射,这样就可以区分使用的是PostPolicy还是VideoPolicy

$user->can('view', $post);
$user->can('view', $video);


那么create方法呢, 我们可以传递一个类名给 can 方法。当授权动作时,这个类名将被用来判断使用哪个策略

$user->can('create', Post::class);
$user->can('view', Video::class);


### 2.3.2 通过中间件

1. 通过隐式模型绑定,指定模型动作

use App\Post;

Route::put('/post/{post}', function (Post $post) {
// 当前用户可以更新博客...
})->middleware('can:update,post');


关于隐式模型绑定,这里顺便提一下,更详细的请自己查询。

首先可以定义这样一条路由

Route::get('/test/{post}','TestController@test');


控制器引入模型文件

use App\Http\Model\Post;


test方法传入实例,并类型提示
public function test(Post $post)
{   
    http_response_code(500);
    dd($post);
}
我们访问这样的一条路由 http://xxxx.com/test/1 ,将dd()出id=1 的post实例,我们并没有专门的实例化post这个model,但是我们自动返回了id=参数的post实例

2. 不需要指定模型的动作

Route::post('/post', function () {
// 当前用户可以创建博客...
})->middleware('can:create,App\Post');


### 2.3.3 通过控制器辅助函数autorize()

如果动作不被授权,authorize 方法会抛出 Illuminate\Auth\Access\AuthorizationException 异常,然后被 Laravel 默认的异常处理器转化为带有 403 状态码的 HTTP 响应:

1. 指定模型动作

public function update(Request $request, Post $post)
{
$this->authorize('update', $post);

// 当前用户可以更新博客...

}


2. 不指定模型动作

public function create(Request $request)
{
$this->authorize('create', Post::class);

// 当前用户可以新建博客...

}


### 2.3.4 通过blade模板

可以使用@can 和 @cannot

1. 指定模型动作

@can('update', $post)

@elsecan('create', $post)

@endcan

@cannot('update', $post)

@elsecannot('create', $post)

@endcannot


2. 不指定模型动作

@can('create', App\Post::class)

@endcan

@cannot('create', App\Post::class)

@endcannot


实际上@can 和@cannot 提供了方便的缩写, 分别等同于下面写法

@if (Auth::user()->can('update', $post))

@endif

@unless (Auth::user()->can('update', $post))

@endunless
```