Laravel Permission 实现 RBAC 权限实操详
版本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
更新,因为没有权限,所以401
image.png
删除操作是有的,
image.png
全部搞定,如果你有不懂的请评论回复我