laravel

Laravel Permission 实现 RBAC 权限实操详

2018-06-14  本文已影响577人  空气KQ

版本5.6,centos7,php7.2
更新2018-6-26

了解laravel Permission

https://packagist.org/packages/spatie/laravel-permission
第一篇laravel Permission:https://www.jianshu.com/p/434bf3a8ebd0

原理

管理员通过多态找到角色,角色找到权限是通过多对多操作

操作

创建中间件

这里定义2个验证
第一个是验证是否具有管理员
第二个是验证是否具有这个操作文章权限

php artisan make:middleware AdminMiddleware
php artisan make:middleware ClearanceMiddleware

创建控制器

4个资源控制器

php artisan make:controller PostController  --resource
php artisan make:controller UserHandleController  --resource
php artisan make:controller RoleController  --resource
php artisan make:controller PermissionController  --resource

创建路由

Route::resource('posts', 'PostController');
Route::resource('users', 'UserHandleController');
Route::resource('permissions', 'PermissionController');
Route::resource('roles', 'RoleController');

使用默认的用户认证,方便测试

php artisan make:auth

注册2个用户


image.png

中间件暂时先不写任何

规则控制器书写

管理权限规则,有规则,有角色,最后才有管理员操作,所以需要设置规则
PermissionController.php
增删改查
放代码,阅读肯定费尽,现在直接来看界面吧

index页面


image.png
   public function index() {
         $permissions = Permission::all(); // 获取所有权限

         return view('permissions.index')->with('permissions', $permissions);
     }

前端文件不放进来,晚点打包给大家,这里暂时不加任何的中间件控制,都没有权限,加了肯定访问不了,所以放最后

Add


image.png

这里我们增加一个可以追加入到角色规则中间件表里面,意思方便,如果新增规则,直接分配到角色里面

public function create() {
         $roles = Role::get(); // 获取所有角色
         return view('permissions.create')->with('roles', $roles);
     }

如果已经有角色了,则添加界面如下


image.png

这里角色是多选的


image.png

添加的store

public function store(Request $request) {
         $this->validate($request, [
             'name'=>'required|max:40',
         ]);

         $name = $request['name'];
         $permission = new Permission();
         $permission->name = $name;

         $roles = $request['roles'];

         $permission->save();

         if (!empty($request['roles'])) { // 如果选择了角色
             foreach ($roles as $role) {
                 $r = Role::where('id', '=', $role)->firstOrFail(); // 将输入角色和数据库记录进行匹配

                 $permission = Permission::where('name', '=', $name)->first(); // 将输入权限与数据库记录进行匹配
                 $r->givePermissionTo($permission);
             }
         }

        return redirect()->route('permissions.index')
        ->with('flash_message','Permission'. $permission->name.' added!');

     }

givePermissionTo这个方法
内容有点多,感觉他写的复杂了,先不做详细了。

public function givePermissionTo(...$permissions)
    {
        //集合对象
       $permissions = collect($permissions)
           //flatten() 方法将多维度的集合变成一维的:
            ->flatten()
        //map 方法遍历集合并传递每个值给给定回调。该回调可以修改数据项并返回,从而生成一个新的经过修改的集合
            ->map(function ($permission) {
                return $this->getStoredPermission($permission);
            })
            ->each(function ($permission) {
                $this->ensureModelSharesGuard($permission);
            })
            ->all();

        $this->permissions()->saveMany($permissions);

        $this->forgetCachedPermissions();

        return $this;
    }

getStorePermission()

protected function getStoredPermission($permissions)
    {
        if (is_numeric($permissions)) {
            return app(Permission::class)->findById($permissions, $this->getDefaultGuardName());
        }

        if (is_string($permissions)) {
            return app(Permission::class)->findByName($permissions, $this->getDefaultGuardName());
        }

        if (is_array($permissions)) {
            return app(Permission::class)
                ->whereIn('name', $permissions)
                ->whereIn('guard_name', $this->getGuardNames())
                ->get();
        }

        return $permissions;
    }

如果增加了角色选择会执行如下的SQL语句

insert into `permissions` (`guard_name`, `name`, `updated_at`, `created_at`) 
values ('web', 'Post Handle', '2018-06-14 02:50:58', '2018-06-14 02:50:58')
//加入第一个角色中间表
select * from `roles` where `id` = '2' limit 1
select * from `permissions` where `name` = 'Post Handle' limit 1
insert into `role_has_permissions` (`permission_id`, `role_id`) values ('5', '2')
//加入第2个角色中间表
select * from `roles` where `id` = '3' limit 1
select * from `permissions` where `name` = 'Post Handle' limit 1
insert into `role_has_permissions` (`permission_id`, `role_id`) values ('5', '3')

Edit


image.png
 public function edit($id) {
         $permission = Permission::findOrFail($id);

         return view('permissions.edit', compact('permission'));
     }
public function update(Request $request, $id) {
         $permission = Permission::findOrFail($id);
         $this->validate($request, [
             'name'=>'required|max:40',
         ]);
         $input = $request->all();
         $permission->fill($input)->save();
        //个人喜欢控制器地址或者是路由地址,不喜欢用url,因为这个是写死的,很容易出问题,如果迁移的话
         return redirect()->route('permissions.index')
             ->with('flash_message',
              'Permission'. $permission->name.' updated!');

     }

角色控制器

管理员对应角色

index界面


image.png
 public function index() {
       $roles = Role::all();// 获取所有角色

       return view('roles.index')->with('roles', $roles);
   }

Add


image.png
 public function create() {
       $permissions = Permission::all();// 获取所有权限

       return view('roles.create', ['permissions'=>$permissions]);
   }
 public function store(Request $request) {
       //验证 name 和 permissions 字段
       $this->validate($request, [
               'name'=>'required|unique:roles|max:10',
               'permissions' =>'required',
           ]
       );

       $name = $request['name'];
       $role = new Role();
       $role->name = $name;

       $permissions = $request['permissions'];

       $role->save();
        $p_all = Permission::all()->pluck('id');//获取所有权限
        $p = Permission::whereIn('id', $permissions)->pluck('id');
        //移除
        $role->revokePermissionTo($p_all);
        //附加
        $role->givePermissionTo($p);
       return redirect()->route('roles.index')
           ->with('flash_message',
            'Role'. $role->name.' added!');
   }

SQL语句

//检查是否重复
select count(*) as aggregate from `roles` where `name` = '创建者
//插入
insert into `roles` (`guard_name`, `name`, `updated_at`, `created_at`) 
values ('web', '创建者', '2018-06-14 03:27:05', '2018-06-14 03:27:05')
//查询这个规则的ID:2
select * from `permissions` where `id` = '2' limit 1
//查询刚才创建的名字,
select * from `roles` where `name` = '创建者' limit 1
//加入中间表
insert into `role_has_permissions` (`permission_id`, `role_id`) values ('2', '5')

我们最终的目地是要清楚原理,代码只是辅助而已,执行后的才是最重要的,感觉这里SQL执行的有点多,

Edit


image.png
 public function edit($id) {
       $role = Role::findOrFail($id);
       $permissions = Permission::all();

       return view('roles.edit', compact('role', 'permissions'));
   }
public function update(Request $request, $id) {

       $role = Role::findOrFail($id); // 通过给定id获取角色
       // 验证 name 和 permission 字段
       $this->validate($request, [
           'name'=>'required|max:10|unique:roles,name,'.$id,
           'permissions' =>'required',
       ]);

       $input = $request->except(['permissions']);
       $permissions = $request['permissions'];
       $role->fill($input)->save();

      //移除。2018-6-26更新
        $p = Permission::whereIn('id', $permissions)->pluck('id');
        $role->givePermissionTo($p);

       foreach ($permissions as $permission) {
           $p = Permission::where('id', '=', $permission)->firstOrFail(); //从数据库中获取相应权限
           $role->givePermissionTo($p);  // 分配权限到角色
       }
      return redirect()->route('roles.index') ->with('flash_message', 'Role'. $role->name.' updated!');
   }

这个时候我并没有更新然后权限,执行下,执行了如下SQL语句

select count(*) as aggregate from `roles` where `name` = '创建者' and `id` <> '5'
select * from `permissions`

//之前是遍历,现在已经修改。
delete from `role_has_permissions` where `role_id` ='5' and `permission_id` in (1,2,3,4,5)
select * from `permissions` where `id` = '2' limit 1
insert into `role_has_permissions` (`permission_id`, `role_id`) values ('2', '5')

现在我增加了权限规则


image.png

执行如下SQL语句

select * from `roles` where `roles`.`id` = '5' limit 1
select count(*) as aggregate from `roles` where `name` = '创建者' and `id` <> '5'
select * from `permissions`
//这里我感觉他为什么不一次性执行一条记录呢
delete from `role_has_permissions` where `role_id` = '5' and `permission_id` in ('1')
delete from `role_has_permissions` where `role_id` = '5' and `permission_id` in ('2')
delete from `role_has_permissions` where `role_id` = '5' and `permission_id` in ('3')
delete from `role_has_permissions` where `role_id` = '5' and `permission_id` in ('4')
delete from `role_has_permissions` where `role_id` = '5' and `permission_id` in ('5')
//可以改成
//delete from `role_has_permissions` where `role_id` = '5' and `permission_id` in (1,2,3,4,5);
//更刚才没有区别,只是多了记录而已,第二个记录没有查询丙炔更新
select * from `permissions` where `id` = '1' limit 1
insert into `role_has_permissions` (`permission_id`, `role_id`) values ('1', '5')
select * from `permissions` where `id` = '2' limit 1
insert into `role_has_permissions` (`permission_id`, `role_id`) values ('2', '5')
select * from `permissions` where `id` = '3' limit 1
insert into `role_has_permissions` (`permission_id`, `role_id`) values ('3', '5')

管理员控制器

这里默认就是user表,这个设计可以改成你的任意多用户,可以参考多用户认证
可以看我这篇
https://www.jianshu.com/p/829f6f66ad6f

index


image.png
public function index() {
    //Get all users and pass it to the view
        $users = User::all();
        return view('users.index')->with('users', $users);
    }

Add


image.png
public function create() {
        // 获取所有角色并将其传递到视图
        $roles = Role::get();
        return view('users.create', ['roles'=>$roles]);
    }
public function store(Request $request) {
        // 验证 name、email 和 password 字段
        $this->validate($request, [
            'name'=>'required|max:120',
            'email'=>'required|email|unique:users',
            'password'=>'required|min:6|confirmed'
        ]);

        $user = User::create($request->only('email', 'name', 'password')); //只获取 email、name、password 字段

        $roles = $request['roles']; // 获取输入的角色字段
        // 检查是否某个角色被选中
        if (isset($roles)) {
            foreach ($roles as $role) {
                $role_r = Role::where('id', '=', $role)->firstOrFail();
                $user->assignRole($role_r); //Assigning role to user
            }
        }
        // 重定向到 users.index 视图并显示消息
        return redirect()->route('users.index')
            ->with('flash_message',
             'User successfully added.');
    }

执行的SQL语句

select count(*) as aggregate from `users` where `email` = 'poster@qq.com'

insert into `users` (`email`, `name`, `password`, `updated_at`, `created_at`) values ('poster@qq.com', 'poster', '123456', '2018-06-14 03:52:48', '2018-06-14 03:52:48')

select * from `roles` where `id` = '2' limit 1
insert into `model_has_roles` (`model_id`, `model_type`, `role_id`) values ('3', 'App\User', '2')
select * from `roles` where `id` = '3' limit 1
insert into `model_has_roles` (`model_id`, `model_type`, `role_id`) values ('3', 'App\User', '3')
select * from `roles` where `id` = '5' limit 1
insert into `model_has_roles` (`model_id`, `model_type`, `role_id`) values ('3', 'App\User', '5')

model_type根据你的用户认证取得来获得,如果你觉得App\User名字不好,你可以指定
AppServiceProvider.php

use Illuminate\Database\Eloquent\Relations\Relation;
public function boot(){
    Relation::morphMap([
        'posts' => 'App\Post',
        'videos' => 'App\Video',
  ]);
}
image.png

Edit


image.png
  public function edit($id) {
        $user = User::findOrFail($id); // 通过给定id获取用户
        $roles = Role::get(); // 获取所有角色

        return view('users.edit', compact('user', 'roles')); // 将用户和角色数据传递到视图

    }

  public function update(Request $request, $id) {
        $user = User::findOrFail($id); // 通过id获取给定角色

        // 验证 name, email 和 password 字段
        $this->validate($request, [
            'name'=>'required|max:120',
            'email'=>'required|email|unique:users,email,'.$id,
            'password'=>'required|min:6|confirmed'
        ]);
        $input = $request->only(['name', 'email', 'password']); // 获取 name, email 和 password 字段
        $roles = $request['roles']; // 获取所有角色
        $user->fill($input)->save();

        if (isset($roles)) {
            $user->roles()->sync($roles);  // 如果有角色选中与用户关联则更新用户角色
        } else {
            //多态关联删除
            $user->roles()->detach(); // 如果没有选择任何与用户关联的角色则将之前关联角色解除
        }
        return redirect()->route('users.index')
            ->with('flash_message',
             'User successfully edited.');
    }

少一个权限规则


image.png

一删除一增加


image.png

这里发现,他既然把单个删除操作,全部更换成了一条语句

Delete

  public function destroy($id) {
        // 通过给定id获取并删除用户
        $user = User::findOrFail($id);
        $user->delete();

        return redirect()->route('users.index')
            ->with('flash_message',
             'User successfully deleted.');
    }

会把关联的多态删除
HasRoles看到如下

  public static function bootHasRoles()
    {
        static::deleting(function ($model) {
            if (method_exists($model, 'isForceDeleting') && ! $model->isForceDeleting()) {
                return;
            }

            $model->roles()->detach();
        });
    }

上面的所有删除,都会把关联的删除掉,这个时候不用担心没有删除掉了,留下辣圾

现在我们看下我们数据情况


image.png
image.png
image.png

超级管理员用户是kongqi@qq.com
bst@qq.com只有创建和删除操作,没有更新
好了现在写2个中间件

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
use App\User;

class AdminMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
     public function handle($request, Closure $next)
    {
        $user = User::all()->count();

        if (!($user == 1)) {
            if (!Auth::user()->hasPermissionTo('Admin')) // 是否是管理员
        {
                abort('401');
            }
        }

        return $next($request);
    }
}

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
class ClearanceMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
     public function handle($request, Closure $next) {
           if (Auth::user()->hasPermissionTo('Root'))
           {
               return $next($request);  // 超级管理员具备所有权限
           }

           if ($request->is('posts/create')) // 文章发布权限
           {
               if (!Auth::user()->hasPermissionTo('Create Post'))
               {
                   abort('401');
               }
               else {
                   return $next($request);
               }
           }

           if ($request->is('posts/*/edit')) // 文章编辑权限
           {
               if (!Auth::user()->hasPermissionTo('Edit Post')) {
                   abort('401');
               } else {
                   return $next($request);
               }
           }

           if ($request->isMethod('Delete')) // 文章删除权限
           {
               if (!Auth::user()->hasPermissionTo('Delete Post')) {
                   abort('401');
               }
               else
               {
                   return $next($request);
               }
           }

           return $next($request);
       }
}

这里只是演示的中间件,那个文章验证中间件,肯定是要写一个自动的,这种手动写法太猥琐了。

加入中间件队
Kernel.php

'isAdmin' =>  \App\Http\Middleware\AdminMiddleware::class,
 'clearance' =>  \App\Http\Middleware\ClearanceMiddleware::class

文章的控制器

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Post;
use Auth;
use Session;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function __construct() {
        $this->middleware(['auth','isadmin','clearance'])->except('index', 'show');
    }
    public function index() {
       $posts = Post::orderby('id', 'desc')->paginate(5); //show only 5 items at a time in descending order
       return view('posts.index', compact('posts'));
   }

    /**
       * 显示创建文章表单
       *
       * @return \Illuminate\Http\Response
       */
      public function create() {
          return view('posts.create');
      }

      /**
       * 存储新增文章
       *
       * @param  \Illuminate\Http\Request  $request
       * @return \Illuminate\Http\Response
       */
      public function store(Request $request) {

          //验证 title 和 body 字段
          $this->validate($request, [
              'title'=>'required|max:100',
              'body' =>'required',
          ]);

          $title = $request['title'];
          $body = $request['body'];

          $post = Post::create($request->only('title', 'body'));

          // 基于保存结果显示成功消息
          return redirect()->route('posts.index')
              ->with('flash_message', 'Article,
               '. $post->title.' created');
      }

      /**
       * 显示指定资源
       *
       * @param  int  $id
       * @return \Illuminate\Http\Response
       */
      public function show($id) {
          $post = Post::findOrFail($id); //通过 id = $id 查找文章
          return view ('posts.show', compact('post'));
      }

      /**
       * 显示编辑文章表单
       *
       * @param  int  $id
       * @return \Illuminate\Http\Response
       */
      public function edit($id) {
          $post = Post::findOrFail($id);

          return view('posts.edit', compact('post'));
      }

      /**
       * 更新文章
       *
       * @param  \Illuminate\Http\Request  $request
       * @param  int  $id
       * @return \Illuminate\Http\Response
       */
      public function update(Request $request, $id) {
          $this->validate($request, [
              'title'=>'required|max:100',
              'body'=>'required',
          ]);

          $post = Post::findOrFail($id);
          $post->title = $request->input('title');
          $post->body = $request->input('body');
          $post->save();

          return redirect()->route('posts.show',
              $post->id)->with('flash_message',
              'Article, '. $post->title.' updated');

      }

      /**
       * 删除文章
       *
       * @param  int  $id
       * @return \Illuminate\Http\Response
       */
      public function destroy($id) {
          $post = Post::findOrFail($id);
          $post->delete();

          return redirect()->route('posts.index')
              ->with('flash_message',
                  'Article successfully deleted');

      }
}

bst@qq.com登录之后

image.png
创建
image.png
image.png
更新,因为没有权限,所以401
image.png
删除操作是有的,
image.png
全部搞定,如果你有不懂的请评论回复我

以上文件下载
https://pan.baidu.com/s/1F7StLQLfPAzOHvorhbbTmA

上一篇下一篇

猜你喜欢

热点阅读