基于 Laravel Permission 扩展包在项目中轻松实现 RBAC 权限管理功能
一直想整理出一篇单独在 Laravel 中基于 RBAC 实现权限管理的教程,今天总算是交上这份作业了,开始之前,先祭出最终用户权限管理的效果图镇场子:
项目初始化
下面正式开始今天的作业,我们基于由 Spatie 维护的 Laravel Permission 扩展包来实现 RBAC 权限管理,Spatie 出品,必属精品。首先需要安装一个干净的 Laravel 项目,然后在项目根目录下通过 Composer 来安装扩展包依赖:
1composer create-project laravel/laravel permission --prefer-dist
2cd permission
3composer require spatie/laravel-permission
创建数据表
先通过如下命令将扩展包提供的数据库迁移文件发布到 database/migrations
目录下:
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
然后不要忘了修改 .env
中的数据库配置以匹配本地数据库设置,完成这一步之后就可以运行下面的命令根据数据库迁移文件生成相应的数据表了:
php artisan migrate
运行成功后查看数据库会发现新生成了如下数据表:
配置文件
接下来将扩展包提供的权限配置文件 permission.php
发布到 config
目录下以便对默认配置进行修改:
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"
其中主要包含了默认权限、角色模型类以及对应数据表配置,一般而言,保持默认配置即可。
模型类调整
在 User
模型类中使用 HasRoles
trait 提供权限相关方法:
安装 Laravel Collective HTML
我们通过 Composer 安装这个扩展包依赖以便后续构建视图页面所需表单:
1composer require laravelcollective/html
基本使用
完成上述初始化工作后,接下来我们来看一下如何使用 Laravel Permission 扩展包提供的方法来实现一些基本操作。
我们可以通过以下方式创建新的角色和权限:
1use Spatie\Permission\Models\Role;
2use Spatie\Permission\Models\Permission;
3
4$role = Role::create(['name' => 'writer']);
5$permission = Permission::create(['name' => 'edit articles']);
通过调用用户实例上的动态属性 permissions
获取用户所有权限:
$permissions = $user->permissions;
通过 pluck
方法获取用户角色名称:
$roles = $user->roles()->pluck('name');
还可以通过 Blade 指令验证登录用户是否拥有给定角色:
1@role('writer')
2 I'm a writer!
3@else
4 I'm not a writer...
5@endrole
6
7@hasrole('writer')
8 I'm a writer!
9@else
10 I'm not a writer...
11@endhasrole
12
13@hasanyrole(Role::all())
14 I have one or more of these roles!
15@else
16 I have none of these roles...
17@endhasanyrole
18
19@hasallroles(Role::all())
20 I have all of these roles!
21@else
22 I don't have all of these roles...
23@endhasallroles
通过 @can
指令验证用户是否拥有给定权限:
@can('Edit Post')
2 I have permission to edit
3@endcan
使用实例
下面我们以为用户分配文章操作权限为例来演示如何在实践中使用 Laravel Permission 扩展包实现 RBAC 权限管理。首先我们通过如下 Artisan 命令生成用户认证相关脚手架代码:
1php artisan make:auth
上述指令会生成用户登录注册所需的路由、控制器、视图等代码文件。我们需要对生成的代码做少许调整:
登录注册控制器调整
修改 RegisterController
控制器的 create
方法如下:
protected function create(array $data)
2{
3 return User::create([
4 'name' => $data['name'],
5 'email' => $data['email'],
6 'password' => $data['password'],
7 ]);
8}
转而在 User
模型类中新增修改器方法以便在保存时对密码字段进行加密处理:
public function setPasswordAttribute($password) {
2 $this->attributes['password'] = bcrypt($password);
3}
修改 LoginController
和 RegisterController
控制器的 redirectTo
属性:
protected $redirectTo = '/';
布局视图文件调整
接下来编辑 resources/views/layouts/app.blade.php
模板文件,新增一个下拉「Admin」链接用于查看所有用户和错误文件,只有具备「Admin」角色的用户才可以看到此链接。此外,我们还创建了一个自定义的 styles.css
文件用于渲染 resources/views/posts/index.blade.php
视图样式:
<!DOCTYPE html>
2<html lang="{{ config('app.locale') }}">
3<head>
4 <meta charset="utf-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta name="viewport" content="width=device-width, initial-scale=1">
7
8 <!-- CSRF Token -->
9 <meta name="csrf-token" content="{{ csrf_token() }}">
10
11 <title>{{ config('app.name', 'Laravel') }}</title>
12
13 <!-- Styles -->
14 <link href="{{ asset('css/styles.css') }}" rel="stylesheet">
15
16 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
17
18 <!-- Scripts -->
19 <script>
20 window.Laravel = {!! json_encode([
21 'csrfToken' => csrf_token(),
22 ]) !!};
23 </script>
24 <script src="https://use.fontawesome.com/9712be8772.js"></script>
25</head>
26<body>
27 <div id="app">
28 <nav class="navbar navbar-default navbar-static-top">
29 <div>
30 <div>
31
32 <!-- Collapsed Hamburger -->
33 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse">
34 <span>Toggle Navigation</span>
35 <span></span>
36 <span></span>
37 <span></span>
38 </button>
39
40 <!-- Branding Image -->
41 <a href="{{ url('/') }}">
42 {{ config('app.name', 'Laravel') }}
43 </a>
44 </div>
45
46 <div class="collapse navbar-collapse" id="app-navbar-collapse">
47 <!-- 导航条左边 -->
48 <ul class="nav navbar-nav">
49 <li><a href="{{ url('/') }}">Home</a></li>
50 @if (!Auth::guest())
51 <li><a href="{{ route('posts.create') }}">New Article</a></li>
52 @endif
53 </ul>
54
55 <!-- 导航条右边 -->
56 <ul class="nav navbar-nav navbar-right">
57 <!-- 登录注册链接 -->
58 @if (Auth::guest())
59 <li><a href="{{ route('login') }}">Login</a></li>
60 <li><a href="{{ route('register') }}">Register</a></li>
61 @else
62 <li>
63 <a href="#" data-toggle="dropdown" role="button" aria-expanded="false">
64 {{ Auth::user()->name }} <span></span>
65 </a>
66
67 <ul role="menu">
68 <li>
69 @role('Admin') {{-- Laravel-permission blade 辅助函数 --}}
70 <a href="#"><i class="fa fa-btn fa-unlock"></i>Admin</a>
71 @endrole
72 <a href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
73 Logout
74 </a>
75
76 <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
77 {{ csrf_field() }}
78 </form>
79 </li>
80 </ul>
81 </li>
82 @endif
83 </ul>
84 </div>
85 </div>
86 </nav>
87
88 @if(Session::has('flash_message'))
89 <div>
90 <div class="alert alert-success"><em> {!! session('flash_message') !!}</em>
91 </div>
92 </div>
93 @endif
94
95 <div>
96 <div class="col-md-8 col-md-offset-2">
97 @include ('errors.list') {{-- 引入错误文件 --}}
98 </div>
99 </div>
100
101 @yield('content')
102
103 </div>
104
105 <!-- Scripts -->
106 <script src="{{ asset('js/app.js') }}"></script>
107</body>
108</html>
创建错误文件视图模板文件 resources/views/errors/list.blade.php
:
@if (count($errors) > 0)
2 <div class="alert alert-danger">
3 <ul>
4 @foreach ($errors->all() as $error)
5 <li>{{ $error }}</li>
6 @endforeach
7 </ul>
8 </div>
9@endif
编写 public/css/styles.css
文件内容如下:
p.teaser {
2 text-indent: 30px;
3}
文章相关资源类
处理好布局视图文件后,接下来开始正式编码工作。首先创建文章模型类及其对应数据库迁移文件:
1php artisan make:model Post -m
然后修改新生成的 CreatePostsTable
迁移类:
use Illuminate\Support\Facades\Schema;
2use Illuminate\Database\Schema\Blueprint;
3use Illuminate\Database\Migrations\Migration;
4
5class CreatePostsTable extends Migration
6{
7 /**
8 * Run the migrations.
9 *
10 * @return void
11 */
12 public function up()
13 {
14 Schema::create('posts', function (Blueprint $table) {
15 $table->increments('id');
16 $table->string('title');
17 $table->text('body');
18 $table->timestamps();
19 });
20 }
21
22 /**
23 * Reverse the migrations.
24 *
25 * @return void
26 */
27 public function down()
28 {
29 Schema::dropIfExists('posts');
30 }
31}
运行如下命令生成对应文章数据表:
1php artisan migrate
编辑 Post
模型类以支持批量赋值:
class Post extends Model
2{
3 protected $fillable = ['title', 'body'];
4}
接下来创建文章资源控制器:
1php artisan make:controller PostController --resource
编写新生成的 PostController
代码如下:
<?php
2namespace App\Http\Controllers;
3
4use Illuminate\Http\Request;
5
6use App\Post;
7use Auth;
8use Session;
9
10class PostController extends Controller {
11
12 public function __construct() {
13 $this->middleware(['auth', 'clearance'])->except('index', 'show');
14 }
15
16 /**
17 * 显示文章列表
18 *
19 * @return \Illuminate\Http\Response
20 */
21 public function index() {
22 $posts = Post::orderby('id', 'desc')->paginate(5); //show only 5 items at a time in descending order
23
24 return view('posts.index', compact('posts'));
25 }
26
27 /**
28 * 显示创建文章表单
29 *
30 * @return \Illuminate\Http\Response
31 */
32 public function create() {
33 return view('posts.create');
34 }
35
36 /**
37 * 存储新增文章
38 *
39 * @param \Illuminate\Http\Request $request
40 * @return \Illuminate\Http\Response
41 */
42 public function store(Request $request) {
43
44 //验证 title 和 body 字段
45 $this->validate($request, [
46 'title'=>'required|max:100',
47 'body' =>'required',
48 ]);
49
50 $title = $request['title'];
51 $body = $request['body'];
52
53 $post = Post::create($request->only('title', 'body'));
54
55 // 基于保存结果显示成功消息
56 return redirect()->route('posts.index')
57 ->with('flash_message', 'Article,
58 '. $post->title.' created');
59 }
60
61 /**
62 * 显示指定资源
63 *
64 * @param int $id
65 * @return \Illuminate\Http\Response
66 */
67 public function show($id) {
68 $post = Post::findOrFail($id); //通过 id = $id 查找文章
69
70 return view ('posts.show', compact('post'));
71 }
72
73 /**
74 * 显示编辑文章表单
75 *
76 * @param int $id
77 * @return \Illuminate\Http\Response
78 */
79 public function edit($id) {
80 $post = Post::findOrFail($id);
81
82 return view('posts.edit', compact('post'));
83 }
84
85 /**
86 * 更新文章
87 *
88 * @param \Illuminate\Http\Request $request
89 * @param int $id
90 * @return \Illuminate\Http\Response
91 */
92 public function update(Request $request, $id) {
93 $this->validate($request, [
94 'title'=>'required|max:100',
95 'body'=>'required',
96 ]);
97
98 $post = Post::findOrFail($id);
99 $post->title = $request->input('title');
100 $post->body = $request->input('body');
101 $post->save();
102
103 return redirect()->route('posts.show',
104 $post->id)->with('flash_message',
105 'Article, '. $post->title.' updated');
106
107 }
108
109 /**
110 * 删除文章
111 *
112 * @param int $id
113 * @return \Illuminate\Http\Response
114 */
115 public function destroy($id) {
116 $post = Post::findOrFail($id);
117 $post->delete();
118
119 return redirect()->route('posts.index')
120 ->with('flash_message',
121 'Article successfully deleted');
122
123 }
124}
最后注册相应路由到 app/routes/web.php
:
Route::get('/', 'PostController@index')->name('home');
2Route::resource('posts', 'PostController');
根据 PostController
控制器提供的方法,需要创建四个相应视图:resources\views\posts\index.blade.php
、resources\views\posts\create.blade.php
、resources\views\posts\show.blade.php
和 \resources\views\posts\edit.blade.php
。
编写 index.blade.php
代码如下:
@extends('layouts.app')
2@section('content')
3 <div>
4 <div>
5 <div class="col-md-10 col-md-offset-1">
6 <div class="panel panel-default">
7 <div><h3>Posts</h3></div>
8 <div>Page {{ $posts->currentPage() }} of {{ $posts->lastPage() }}</div>
9 @foreach ($posts as $post)
10 <div>
11 <li style="list-style-type:disc">
12 <a href="{{ route('posts.show', $post->id ) }}"><b>{{ $post->title }}</b><br>
13 <p>
14 {{ str_limit($post->body, 100) }} {{-- Limit teaser to 100 characters --}}
15 </p>
16 </a>
17 </li>
18 </div>
19 @endforeach
20 </div>
21 <div>
22 {!! $posts->links() !!}
23 </div>
24 </div>
25 </div>
26 </div>
27@endsection
编写 create.blade.php
代码如下:
@extends('layouts.app')
2
3@section('title', '| Create New Post')
4
5@section('content')
6 <div>
7 <div class="col-md-8 col-md-offset-2">
8
9 <h1>Create New Post</h1>
10 <hr>
11
12 {{-- 使用 Laravel HTML Form Collective 创建表单 --}}
13 {{ Form::open(array('route' => 'posts.store')) }}
14
15 <div>
16 {{ Form::label('title', 'Title') }}
17 {{ Form::text('title', null, array('class' => 'form-control')) }}
18 <br>
19
20 {{ Form::label('body', 'Post Body') }}
21 {{ Form::textarea('body', null, array('class' => 'form-control')) }}
22 <br>
23
24 {{ Form::submit('Create Post', array('class' => 'btn btn-success btn-lg btn-block')) }}
25 {{ Form::close() }}
26 </div>
27 </div>
28 </div>
29
30@endsection
编写 show.blade.php
代码如下:
@extends('layouts.app')
2
3@section('title', '| View Post')
4
5@section('content')
6
7<div>
8
9 <h1>{{ $post->title }}</h1>
10 <hr>
11 <p>{{ $post->body }} </p>
12 <hr>
13 {!! Form::open(['method' => 'DELETE', 'route' => ['posts.destroy', $post->id] ]) !!}
14 <a href="{{ url()->previous() }}" class="btn btn-primary">Back</a>
15 @can('Edit Post')
16 <a href="{{ route('posts.edit', $post->id) }}" class="btn btn-info" role="button">Edit</a>
17 @endcan
18 @can('Delete Post')
19 {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}
20 @endcan
21 {!! Form::close() !!}
22
23</div>
24
25@endsection
最后,编写 edit.blade.php
代码如下:
@extends('layouts.app')
2
3@section('title', '| Edit Post')
4
5@section('content')
6<div>
7
8 <div class="col-md-8 col-md-offset-2">
9
10 <h1>Edit Post</h1>
11 <hr>
12 {{ Form::model($post, array('route' => array('posts.update', $post->id), 'method' => 'PUT')) }}
13 <div>
14 {{ Form::label('title', 'Title') }}
15 {{ Form::text('title', null, array('class' => 'form-control')) }}<br>
16
17 {{ Form::label('body', 'Post Body') }}
18 {{ Form::textarea('body', null, array('class' => 'form-control')) }}<br>
19
20 {{ Form::submit('Save', array('class' => 'btn btn-primary')) }}
21
22 {{ Form::close() }}
23 </div>
24 </div>
25</div>
26
27@endsection
完成上述工作后,再次访问项目首页,页面显示如下:
用户相关资源类
用户对应模型类和数据表已存在,所以我们直接从创建资源控制器开始:
1php artisan make:controller UserController --resource
编写刚生成的 UserController
代码如下:
<?php
2
3namespace App\Http\Controllers;
4
5use Illuminate\Http\Request;
6use App\User;
7use Auth;
8// 引入 laravel-permission 模型
9use Spatie\Permission\Models\Role;
10use Spatie\Permission\Models\Permission;
11// 用于输出一次性信息
12use Session;
13
14class UserController extends Controller {
15
16 public function __construct() {
17 $this->middleware(['auth', 'isAdmin']); // isAdmin 中间件让具备指定权限的用户才能访问该资源
18 }
19
20 /**
21 * 显示用户列表
22 *
23 * @return \Illuminate\Http\Response
24 */
25 public function index() {
26 //Get all users and pass it to the view
27 $users = User::all();
28 return view('users.index')->with('users', $users);
29 }
30
31 /**
32 * 显示创建用户角色表单
33 *
34 * @return \Illuminate\Http\Response
35 */
36 public function create() {
37 // 获取所有角色并将其传递到视图
38 $roles = Role::get();
39 return view('users.create', ['roles'=>$roles]);
40 }
41
42 /**
43 * 在数据库中保存新创建的资源
44 *
45 * @param \Illuminate\Http\Request $request
46 * @return \Illuminate\Http\Response
47 */
48 public function store(Request $request) {
49 // 验证 name、email 和 password 字段
50 $this->validate($request, [
51 'name'=>'required|max:120',
52 'email'=>'required|email|unique:users',
53 'password'=>'required|min:6|confirmed'
54 ]);
55
56 $user = User::create($request->only('email', 'name', 'password')); //只获取 email、name、password 字段
57
58 $roles = $request['roles']; // 获取输入的角色字段
59 // 检查是否某个角色被选中
60 if (isset($roles)) {
61 foreach ($roles as $role) {
62 $role_r = Role::where('id', '=', $role)->firstOrFail();
63 $user->assignRole($role_r); //Assigning role to user
64 }
65 }
66 // 重定向到 users.index 视图并显示消息
67 return redirect()->route('users.index')
68 ->with('flash_message',
69 'User successfully added.');
70 }
71
72 /**
73 * 显示指定用户
74 *
75 * @param int $id
76 * @return \Illuminate\Http\Response
77 */
78 public function show($id) {
79 return redirect('users');
80 }
81
82 /**
83 * 显示编辑用户角色表单
84 *
85 * @param int $id
86 * @return \Illuminate\Http\Response
87 */
88 public function edit($id) {
89 $user = User::findOrFail($id); // 通过给定id获取用户
90 $roles = Role::get(); // 获取所有角色
91
92 return view('users.edit', compact('user', 'roles')); // 将用户和角色数据传递到视图
93
94 }
95
96 /**
97 * 更新数据库中的给定用户
98 *
99 * @param \Illuminate\Http\Request $request
100 * @param int $id
101 * @return \Illuminate\Http\Response
102 */
103 public function update(Request $request, $id) {
104 $user = User::findOrFail($id); // 通过id获取给定角色
105
106 // 验证 name, email 和 password 字段
107 $this->validate($request, [
108 'name'=>'required|max:120',
109 'email'=>'required|email|unique:users,email,'.$id,
110 'password'=>'required|min:6|confirmed'
111 ]);
112 $input = $request->only(['name', 'email', 'password']); // 获取 name, email 和 password 字段
113 $roles = $request['roles']; // 获取所有角色
114 $user->fill($input)->save();
115
116 if (isset($roles)) {
117 $user->roles()->sync($roles); // 如果有角色选中与用户关联则更新用户角色
118 } else {
119 $user->roles()->detach(); // 如果没有选择任何与用户关联的角色则将之前关联角色解除
120 }
121 return redirect()->route('users.index')
122 ->with('flash_message',
123 'User successfully edited.');
124 }
125
126 /**
127 * 删除用户
128 *
129 * @param int $id
130 * @return \Illuminate\Http\Response
131 */
132 public function destroy($id) {
133 // 通过给定id获取并删除用户
134 $user = User::findOrFail($id);
135 $user->delete();
136
137 return redirect()->route('users.index')
138 ->with('flash_message',
139 'User successfully deleted.');
140 }
141}
注册相应路由到 app/routes/web.php
:
Route::resource('users', 'UserController');
根据 UserController
控制器提供的方法需要新增三个对应视图:resources\views\users\index.blade.php
、resources\views\users\create.blade.php
、resources\views\users\edit.blade.php
。
编写 index.blade.php
视图代码如下:
@extends('layouts.app')
2
3@section('title', '| Users')
4
5@section('content')
6
7<div class="col-lg-10 col-lg-offset-1">
8 <h1><i class="fa fa-users"></i> User Administration <a href="{{ route('roles.index') }}" class="btn btn-default pull-right">Roles</a>
9 <a href="{{ route('permissions.index') }}" class="btn btn-default pull-right">Permissions</a></h1>
10 <hr>
11 <div>
12 <table class="table table-bordered table-striped">
13
14 <thead>
15 <tr>
16 <th>Name</th>
17 <th>Email</th>
18 <th>Date/Time Added</th>
19 <th>User Roles</th>
20 <th>Operations</th>
21 </tr>
22 </thead>
23
24 <tbody>
25 @foreach ($users as $user)
26 <tr>
27
28 <td>{{ $user->name }}</td>
29 <td>{{ $user->email }}</td>
30 <td>{{ $user->created_at->format('F d, Y h:ia') }}</td>
31 <td>{{ $user->roles()->pluck('name')->implode(' ') }}</td>{{-- Retrieve array of roles associated to a user and convert to string --}}
32 <td>
33 <a href="{{ route('users.edit', $user->id) }}" class="btn btn-info pull-left" style="margin-right: 3px;">Edit</a>
34
35 {!! Form::open(['method' => 'DELETE', 'route' => ['users.destroy', $user->id] ]) !!}
36 {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}
37 {!! Form::close() !!}
38
39 </td>
40 </tr>
41 @endforeach
42 </tbody>
43
44 </table>
45 </div>
46
47 <a href="{{ route('users.create') }}" class="btn btn-success">Add User</a>
48
49</div>
50
51@endsection
编写 create.blade.php
视图代码如下:
@extends('layouts.app')
2
3@section('title', '| Add User')
4
5@section('content')
6
7<div class='col-lg-4 col-lg-offset-4'>
8
9 <h1><i class='fa fa-user-plus'></i> Add User</h1>
10 <hr>
11
12 {{ Form::open(array('url' => 'users')) }}
13
14 <div>
15 {{ Form::label('name', 'Name') }}
16 {{ Form::text('name', '', array('class' => 'form-control')) }}
17 </div>
18
19 <div>
20 {{ Form::label('email', 'Email') }}
21 {{ Form::email('email', '', array('class' => 'form-control')) }}
22 </div>
23
24 <div>
25 @foreach ($roles as $role)
26 {{ Form::checkbox('roles[]', $role->id ) }}
27 {{ Form::label($role->name, ucfirst($role->name)) }}<br>
28
29 @endforeach
30 </div>
31
32 <div>
33 {{ Form::label('password', 'Password') }}<br>
34 {{ Form::password('password', array('class' => 'form-control')) }}
35
36 </div>
37
38 <div>
39 {{ Form::label('password', 'Confirm Password') }}<br>
40 {{ Form::password('password_confirmation', array('class' => 'form-control')) }}
41
42 </div>
43
44 {{ Form::submit('Add', array('class' => 'btn btn-primary')) }}
45
46 {{ Form::close() }}
47
48</div>
49
50@endsection
编写 edit.blade.php
视图代码如下:
@extends('layouts.app')
2
3@section('title', '| Edit User')
4
5@section('content')
6
7<div class='col-lg-4 col-lg-offset-4'>
8
9 <h1><i class='fa fa-user-plus'></i> Edit {{$user->name}}</h1>
10 <hr>
11
12 {{ Form::model($user, array('route' => array('users.update', $user->id), 'method' => 'PUT')) }}{{-- Form model binding to automatically populate our fields with user data --}}
13
14 <div>
15 {{ Form::label('name', 'Name') }}
16 {{ Form::text('name', null, array('class' => 'form-control')) }}
17 </div>
18
19 <div>
20 {{ Form::label('email', 'Email') }}
21 {{ Form::email('email', null, array('class' => 'form-control')) }}
22 </div>
23
24 <h5><b>Give Role</b></h5>
25
26 <div>
27 @foreach ($roles as $role)
28 {{ Form::checkbox('roles[]', $role->id, $user->roles ) }}
29 {{ Form::label($role->name, ucfirst($role->name)) }}<br>
30
31 @endforeach
32 </div>
33
34 <div>
35 {{ Form::label('password', 'Password') }}<br>
36 {{ Form::password('password', array('class' => 'form-control')) }}
37
38 </div>
39
40 <div>
41 {{ Form::label('password', 'Confirm Password') }}<br>
42 {{ Form::password('password_confirmation', array('class' => 'form-control')) }}
43
44 </div>
45
46 {{ Form::submit('Add', array('class' => 'btn btn-primary')) }}
47
48 {{ Form::close() }}
49
50</div>
51
52@endsection
至此,用户相关控制器和视图已编写完毕,下面来看权限相关资源类。
权限相关资源类
权限对应模型和数据表也已经存在了,所以只需通过以下命令创建权限资源控制器:
1php artisan make:controller PermissionController --resource
编写 PermissionController
控制器代码如下:
<?php
2
3namespace App\Http\Controllers;
4
5use Illuminate\Http\Request;
6
7use Auth;
8
9// 引入 laravel-permission 模型
10use Spatie\Permission\Models\Role;
11use Spatie\Permission\Models\Permission;
12
13use Session;
14
15class PermissionController extends Controller {
16
17 public function __construct() {
18 $this->middleware(['auth', 'isAdmin']); // isAdmin 中间件让具备指定权限的用户才能访问该资源
19
20 }
21
22 /**
23 * 显示权限列表
24 *
25 * @return \Illuminate\Http\Response
26 */
27 public function index() {
28 $permissions = Permission::all(); // 获取所有权限
29
30 return view('permissions.index')->with('permissions', $permissions);
31 }
32
33 /**
34 * 显示创建权限表单
35 *
36 * @return \Illuminate\Http\Response
37 */
38 public function create() {
39 $roles = Role::get(); // 获取所有角色
40
41 return view('permissions.create')->with('roles', $roles);
42 }
43
44 /**
45 * 保存新创建的权限
46 *
47 * @param \Illuminate\Http\Request $request
48 * @return \Illuminate\Http\Response
49 */
50 public function store(Request $request) {
51 $this->validate($request, [
52 'name'=>'required|max:40',
53 ]);
54
55 $name = $request['name'];
56 $permission = new Permission();
57 $permission->name = $name;
58
59 $roles = $request['roles'];
60
61 $permission->save();
62
63 if (!empty($request['roles'])) { // 如果选择了角色
64 foreach ($roles as $role) {
65 $r = Role::where('id', '=', $role)->firstOrFail(); // 将输入角色和数据库记录进行匹配
66
67 $permission = Permission::where('name', '=', $name)->first(); // 将输入权限与数据库记录进行匹配
68 $r->givePermissionTo($permission);
69 }
70 }
71
72 return redirect()->route('permissions.index')
73 ->with('flash_message',
74 'Permission'. $permission->name.' added!');
75
76 }
77
78 /**
79 * 显示给定权限
80 *
81 * @param int $id
82 * @return \Illuminate\Http\Response
83 */
84 public function show($id) {
85 return redirect('permissions');
86 }
87
88 /**
89 * 显示编辑权限表单
90 *
91 * @param int $id
92 * @return \Illuminate\Http\Response
93 */
94 public function edit($id) {
95 $permission = Permission::findOrFail($id);
96
97 return view('permissions.edit', compact('permission'));
98 }
99
100 /**
101 * 更新指定权限
102 *
103 * @param \Illuminate\Http\Request $request
104 * @param int $id
105 * @return \Illuminate\Http\Response
106 */
107 public function update(Request $request, $id) {
108 $permission = Permission::findOrFail($id);
109 $this->validate($request, [
110 'name'=>'required|max:40',
111 ]);
112 $input = $request->all();
113 $permission->fill($input)->save();
114
115 return redirect()->route('permissions.index')
116 ->with('flash_message',
117 'Permission'. $permission->name.' updated!');
118
119 }
120
121 /**
122 * 删除给定权限
123 *
124 * @param int $id
125 * @return \Illuminate\Http\Response
126 */
127 public function destroy($id) {
128 $permission = Permission::findOrFail($id);
129
130 // 让特定权限无法删除
131 if ($permission->name == "Administer roles & permissions") {
132 return redirect()->route('permissions.index')
133 ->with('flash_message',
134 'Cannot delete this Permission!');
135 }
136
137 $permission->delete();
138
139 return redirect()->route('permissions.index')
140 ->with('flash_message',
141 'Permission deleted!');
142
143 }
144}
注册相应路由到 app/routes/web.php
:
Route::resource('permissions', 'PermissionController');
根据 PermissionController
控制器提供的方法,需要创建三个对应视图文件。
首先创建 resources/views/permissions/index.blade.php
文件:
@extends('layouts.app')
2
3@section('title', '| Permissions')
4
5@section('content')
6
7<div class="col-lg-10 col-lg-offset-1">
8 <h1><i class="fa fa-key"></i>Available Permissions
9
10 <a href="{{ route('users.index') }}" class="btn btn-default pull-right">Users</a>
11 <a href="{{ route('roles.index') }}" class="btn btn-default pull-right">Roles</a></h1>
12 <hr>
13 <div>
14 <table class="table table-bordered table-striped">
15
16 <thead>
17 <tr>
18 <th>Permissions</th>
19 <th>Operation</th>
20 </tr>
21 </thead>
22 <tbody>
23 @foreach ($permissions as $permission)
24 <tr>
25 <td>{{ $permission->name }}</td>
26 <td>
27 <a href="{{ URL::to('permissions/'.$permission->id.'/edit') }}" class="btn btn-info pull-left" style="margin-right: 3px;">Edit</a>
28
29 {!! Form::open(['method' => 'DELETE', 'route' => ['permissions.destroy', $permission->id] ]) !!}
30 {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}
31 {!! Form::close() !!}
32
33 </td>
34 </tr>
35 @endforeach
36 </tbody>
37 </table>
38 </div>
39
40 <a href="{{ URL::to('permissions/create') }}" class="btn btn-success">Add Permission</a>
41
42</div>
43
44@endsection
接下来创建 resources/views/permissions/create.blade.php
视图文件:
@extends('layouts.app')
2
3@section('title', '| Create Permission')
4
5@section('content')
6
7<div class='col-lg-4 col-lg-offset-4'>
8
9 <h1><i class='fa fa-key'></i> Add Permission</h1>
10 <br>
11
12 {{ Form::open(array('url' => 'permissions')) }}
13
14 <div>
15 {{ Form::label('name', 'Name') }}
16 {{ Form::text('name', '', array('class' => 'form-control')) }}
17 </div><br>
18 @if(!$roles->isEmpty()) //If no roles exist yet
19 <h4>Assign Permission to Roles</h4>
20
21 @foreach ($roles as $role)
22 {{ Form::checkbox('roles[]', $role->id ) }}
23 {{ Form::label($role->name, ucfirst($role->name)) }}<br>
24
25 @endforeach
26 @endif
27 <br>
28 {{ Form::submit('Add', array('class' => 'btn btn-primary')) }}
29
30 {{ Form::close() }}
31
32</div>
33
34@endsection
最后创建 resources/views/permissions/edit.blade.php
视图文件:
@extends('layouts.app')
2
3@section('title', '| Edit Permission')
4
5@section('content')
6
7<div class='col-lg-4 col-lg-offset-4'>
8
9 <h1><i class='fa fa-key'></i> Edit {{$permission->name}}</h1>
10 <br>
11 {{ Form::model($permission, array('route' => array('permissions.update', $permission->id), 'method' => 'PUT')) }}{{-- Form model binding to automatically populate our fields with permission data --}}
12
13 <div>
14 {{ Form::label('name', 'Permission Name') }}
15 {{ Form::text('name', null, array('class' => 'form-control')) }}
16 </div>
17 <br>
18 {{ Form::submit('Edit', array('class' => 'btn btn-primary')) }}
19
20 {{ Form::close() }}
21
22</div>
23
24@endsection
最后创建角色相关资源类。
角色相关资源类
和权限一样,对应模型类和数据表已经存在,所以也是从创建资源控制器开始:
1php artisan make:controller RoleController --resource
编写刚生成的 RoleController
控制器代码如下:
<?php
2
3namespace App\Http\Controllers;
4
5use Illuminate\Http\Request;
6use Auth;
7// 引入 laravel-permission 模型
8use Spatie\Permission\Models\Role;
9use Spatie\Permission\Models\Permission;
10use Session;
11
12class RoleController extends Controller {
13
14 public function __construct() {
15 $this->middleware(['auth', 'isAdmin']); // isAdmin 中间件让具备指定权限的用户才能访问该资源
16
17 }
18
19 /**
20 * 显示角色列表
21 *
22 * @return \Illuminate\Http\Response
23 */
24 public function index() {
25 $roles = Role::all();// 获取所有角色
26
27 return view('roles.index')->with('roles', $roles);
28 }
29
30 /**
31 * 显示创建角色表单
32 *
33 * @return \Illuminate\Http\Response
34 */
35 public function create() {
36 $permissions = Permission::all();// 获取所有权限
37
38 return view('roles.create', ['permissions'=>$permissions]);
39 }
40
41 /**
42 * 保存新创建的角色
43 *
44 * @param \Illuminate\Http\Request $request
45 * @return \Illuminate\Http\Response
46 */
47 public function store(Request $request) {
48 //验证 name 和 permissions 字段
49 $this->validate($request, [
50 'name'=>'required|unique:roles|max:10',
51 'permissions' =>'required',
52 ]
53 );
54
55 $name = $request['name'];
56 $role = new Role();
57 $role->name = $name;
58
59 $permissions = $request['permissions'];
60
61 $role->save();
62 // 遍历选择的权限
63 foreach ($permissions as $permission) {
64 $p = Permission::where('id', '=', $permission)->firstOrFail();
65 // 获取新创建的角色并分配权限
66 $role = Role::where('name', '=', $name)->first();
67 $role->givePermissionTo($p);
68 }
69
70 return redirect()->route('roles.index')
71 ->with('flash_message',
72 'Role'. $role->name.' added!');
73 }
74
75 /**
76 * 显示指定角色
77 *
78 * @param int $id
79 * @return \Illuminate\Http\Response
80 */
81 public function show($id) {
82 return redirect('roles');
83 }
84
85 /**
86 * 显示编辑角色表单
87 *
88 * @param int $id
89 * @return \Illuminate\Http\Response
90 */
91 public function edit($id) {
92 $role = Role::findOrFail($id);
93 $permissions = Permission::all();
94
95 return view('roles.edit', compact('role', 'permissions'));
96 }
97
98 /**
99 * 更新角色
100 *
101 * @param \Illuminate\Http\Request $request
102 * @param int $id
103 * @return \Illuminate\Http\Response
104 */
105 public function update(Request $request, $id) {
106
107 $role = Role::findOrFail($id); // 通过给定id获取角色
108 // 验证 name 和 permission 字段
109 $this->validate($request, [
110 'name'=>'required|max:10|unique:roles,name,'.$id,
111 'permissions' =>'required',
112 ]);
113
114 $input = $request->except(['permissions']);
115 $permissions = $request['permissions'];
116 $role->fill($input)->save();
117
118 $p_all = Permission::all();//获取所有权限
119
120 foreach ($p_all as $p) {
121 $role->revokePermissionTo($p); // 移除与角色关联的所有权限
122 }
123
124 foreach ($permissions as $permission) {
125 $p = Permission::where('id', '=', $permission)->firstOrFail(); //从数据库中获取相应权限
126 $role->givePermissionTo($p); // 分配权限到角色
127 }
128
129 return redirect()->route('roles.index')
130 ->with('flash_message',
131 'Role'. $role->name.' updated!');
132 }
133
134 /**
135 * 删除指定权限
136 *
137 * @param int $id
138 * @return \Illuminate\Http\Response
139 */
140 public function destroy($id)
141 {
142 $role = Role::findOrFail($id);
143 $role->delete();
144
145 return redirect()->route('roles.index')
146 ->with('flash_message',
147 'Role deleted!');
148
149 }
150}
注册相应路由到 app/routes/web.php
:
Route::resource('roles', 'RoleController');
根据上面的 RoleController
控制器,需要创建三个相应的视图文件。
首先创建 resources/views/roles/index.blade.php
视图文件:
@extends('layouts.app')
2
3@section('title', '| Roles')
4
5@section('content')
6
7<div class="col-lg-10 col-lg-offset-1">
8 <h1><i class="fa fa-key"></i> Roles
9
10 <a href="{{ route('users.index') }}" class="btn btn-default pull-right">Users</a>
11 <a href="{{ route('permissions.index') }}" class="btn btn-default pull-right">Permissions</a></h1>
12 <hr>
13 <div>
14 <table class="table table-bordered table-striped">
15 <thead>
16 <tr>
17 <th>Role</th>
18 <th>Permissions</th>
19 <th>Operation</th>
20 </tr>
21 </thead>
22
23 <tbody>
24 @foreach ($roles as $role)
25 <tr>
26
27 <td>{{ $role->name }}</td>
28
29 <td>{{ str_replace(array('[',']','"'),'', $role->permissions()->pluck('name')) }}</td>{{-- Retrieve array of permissions associated to a role and convert to string --}}
30 <td>
31 <a href="{{ URL::to('roles/'.$role->id.'/edit') }}" class="btn btn-info pull-left" style="margin-right: 3px;">Edit</a>
32
33 {!! Form::open(['method' => 'DELETE', 'route' => ['roles.destroy', $role->id] ]) !!}
34 {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}
35 {!! Form::close() !!}
36
37 </td>
38 </tr>
39 @endforeach
40 </tbody>
41
42 </table>
43 </div>
44
45 <a href="{{ URL::to('roles/create') }}" class="btn btn-success">Add Role</a>
46
47</div>
48
49@endsection
然后创建 resources/views/roles/create.blade.php
视图文件:
@extends('layouts.app')
2
3@section('title', '| Add Role')
4
5@section('content')
6
7<div class='col-lg-4 col-lg-offset-4'>
8
9 <h1><i class='fa fa-key'></i> Add Role</h1>
10 <hr>
11
12 {{ Form::open(array('url' => 'roles')) }}
13
14 <div>
15 {{ Form::label('name', 'Name') }}
16 {{ Form::text('name', null, array('class' => 'form-control')) }}
17 </div>
18
19 <h5><b>Assign Permissions</b></h5>
20
21 <div>
22 @foreach ($permissions as $permission)
23 {{ Form::checkbox('permissions[]', $permission->id ) }}
24 {{ Form::label($permission->name, ucfirst($permission->name)) }}<br>
25
26 @endforeach
27 </div>
28
29 {{ Form::submit('Add', array('class' => 'btn btn-primary')) }}
30
31 {{ Form::close() }}
32
33</div>
34
35@endsection
最后创建 resources/views/roles/edit.blade.php
视图文件:
@extends('layouts.app')
2
3@section('title', '| Edit Role')
4
5@section('content')
6
7<div class='col-lg-4 col-lg-offset-4'>
8 <h1><i class='fa fa-key'></i> Edit Role: {{$role->name}}</h1>
9 <hr>
10
11 {{ Form::model($role, array('route' => array('roles.update', $role->id), 'method' => 'PUT')) }}
12
13 <div>
14 {{ Form::label('name', 'Role Name') }}
15 {{ Form::text('name', null, array('class' => 'form-control')) }}
16 </div>
17
18 <h5><b>Assign Permissions</b></h5>
19 @foreach ($permissions as $permission)
20
21 {{Form::checkbox('permissions[]', $permission->id, $role->permissions ) }}
22 {{Form::label($permission->name, ucfirst($permission->name)) }}<br>
23
24 @endforeach
25 <br>
26 {{ Form::submit('Edit', array('class' => 'btn btn-primary')) }}
27
28 {{ Form::close() }}
29</div>
30
31@endsection
权限中间件
到这里还没有结束,我们在上面的控制器中有用到 isAdmin
和 clearance
中间件,下面需要来创建并注册这两个中间件。
首先创建 AdminMiddleware
中间件:
php artisan make:middleware AdminMiddleware
编写 AdminMiddleware
中间件代码如下:
<?php
2
3namespace App\Http\Middleware;
4
5use Closure;
6use Illuminate\Support\Facades\Auth;
7use App\User;
8
9class AdminMiddleware
10{
11 /**
12 * Handle an incoming request.
13 *
14 * @param \Illuminate\Http\Request $request
15 * @param \Closure $next
16 * @return mixed
17 */
18 public function handle($request, Closure $next)
19 {
20 $user = User::all()->count();
21 if (!($user == 1)) {
22 if (!Auth::user()->hasPermissionTo('Administer roles & permissions')) // 用户是否具备此权限
23 {
24 abort('401');
25 }
26 }
27
28 return $next($request);
29 }
30}
该中间件的作用主要是用于判断指定用户是否具备管理员权限。通过上面的代码,可以看出系统的第一个用户默认是管理员,以防止第一个用户出现权限死锁的情况。
接下来创建另一个中间件 ClearanceMiddleware
:
php artisan make:middleware ClearanceMiddleware
编写 ClearanceMiddleware
代码如下:
<?php
2
3namespace App\Http\Middleware;
4
5use Closure;
6use Illuminate\Support\Facades\Auth;
7
8class ClearanceMiddleware {
9 /**
10 * Handle an incoming request.
11 *
12 * @param \Illuminate\Http\Request $request
13 * @param \Closure $next
14 * @return mixed
15 */
16 public function handle($request, Closure $next) {
17 if (Auth::user()->hasPermissionTo('Administer roles & permissions'))
18 {
19 return $next($request); // 管理员具备所有权限
20 }
21
22 if ($request->is('posts/create')) // 文章发布权限
23 {
24 if (!Auth::user()->hasPermissionTo('Create Post'))
25 {
26 abort('401');
27 }
28 else {
29 return $next($request);
30 }
31 }
32
33 if ($request->is('posts/*/edit')) // 文章编辑权限
34 {
35 if (!Auth::user()->hasPermissionTo('Edit Post')) {
36 abort('401');
37 } else {
38 return $next($request);
39 }
40 }
41
42 if ($request->isMethod('Delete')) // 文章删除权限
43 {
44 if (!Auth::user()->hasPermissionTo('Delete Post')) {
45 abort('401');
46 }
47 else
48 {
49 return $next($request);
50 }
51 }
52
53 return $next($request);
54 }
55}
该中间件的主要作用是判断用户是否具备给定操作的权限。
将上述两个中间件注册到 app/Http/kernel.php
的 $routeMiddleware
属性中:
protected $routeMiddleware = [
2 ... // 其他中间件
3 'isAdmin' => AdminMiddleware::class,
4 'clearance' => ClearanceMiddleware::class
5];
最后我们为 401
状态码编写一个错误页面 resources\views\errors\401.blade.php
:
@extends('layouts.app')
2
3@section('content')
4 <div class='col-lg-4 col-lg-offset-4'>
5 <h1><center>401<br>
6 ACCESS DENIED</center></h1>
7 </div>
8
9@endsection
至此,我们已经完成了所有的编码工作,接下来对上面编写的代码进行功能测试。
功能测试
根据前面的 isAdmin
中间件实现逻辑,系统第一个用户默认具备管理员权限,这样我们就可以通过这个用户创建必要的权限和角色。
在 http://permission.test/permissions
页面新增四个权限 —— Create Post、Edit Post、Delete Post 以及 Administer roles & permissions:
接下来在 http://permission.test/roles
页面创建几个具备相应权限的角色:
最后在 http://permission.test/users
页面将「Admin」角色分配给当前登录用户:
分配成功之后在顶部导航下拉列表中就可以看到「Admin」选项了:
现在,可以点击「New Article」链接发布新文章了:
这表示我们已经成功给用户分配了权限。在文章详情页,该用户(管理员)也具备增删改所有权限:
至此,我们基于 RBAC 实现权限管理的教程已经全部完结,是不是挺简单的?
61 条评论
#61 lxyzsl 评论于 5个月前这个路由权限都是写死的,不方便管理啊,如果创建了一个新角色且不是要重新修改路由权限了