基于 Laravel Permission 扩展包在项目中轻松实现 RBAC 权限管理功能
一直想整理出一篇单独在 Laravel 中基于 RBAC 实现权限管理的教程,今天总算是交上这份作业了,开始之前,先祭出最终用户权限管理的效果图镇场子:

项目初始化
下面正式开始今天的作业,我们基于由 Spatie 维护的 Laravel Permission 扩展包来实现 RBAC 权限管理,Spatie 出品,必属精品。首先需要安装一个干净的 Laravel 项目,然后在项目根目录下通过 Composer 来安装扩展包依赖:
1composer create-project laravel/laravel permission --prefer-dist2cd permission3composer 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;34$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@else4 I'm not a writer...5@endrole67@hasrole('writer')8 I'm a writer!9@else10 I'm not a writer...11@endhasrole1213@hasanyrole(Role::all())14 I have one or more of these roles!15@else16 I have none of these roles...17@endhasanyrole1819@hasallroles(Role::all())20 I have all of these roles!21@else22 I don't have all of these roles...23@endhasallroles通过 @can 指令验证用户是否拥有给定权限:
@can('Edit Post')2 I have permission to edit3@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">78 <!-- CSRF Token -->9 <meta name="csrf-token" content="{{ csrf_token() }}">1011 <title>{{ config('app.name', 'Laravel') }}</title>1213 <!-- Styles -->14 <link href="{{ asset('css/styles.css') }}" rel="stylesheet">1516 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">1718 <!-- 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>3132 <!-- 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>3940 <!-- Branding Image -->41 <a href="{{ url('/') }}">42 {{ config('app.name', 'Laravel') }}43 </a>44 </div>4546 <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 @endif53 </ul>5455 <!-- 导航条右边 -->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 @else62 <li>63 <a href="#" data-toggle="dropdown" role="button" aria-expanded="false">64 {{ Auth::user()->name }} <span></span>65 </a>6667 <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 @endrole72 <a href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">73 Logout74 </a>7576 <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">77 {{ csrf_field() }}78 </form>79 </li>80 </ul>81 </li>82 @endif83 </ul>84 </div>85 </div>86 </nav>8788 @if(Session::has('flash_message'))89 <div> 90 <div class="alert alert-success"><em> {!! session('flash_message') !!}</em>91 </div>92 </div>93 @endif 9495 <div>96 <div class="col-md-8 col-md-offset-2"> 97 @include ('errors.list') {{-- 引入错误文件 --}}98 </div>99 </div>100101 @yield('content')102103 </div>104105 <!-- 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 @endforeach7 </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;45class CreatePostsTable extends Migration6{7 /**8 * Run the migrations.9 *10 * @return void11 */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 }2122 /**23 * Reverse the migrations.24 *25 * @return void26 */27 public function down()28 {29 Schema::dropIfExists('posts');30 }31}运行如下命令生成对应文章数据表:
1php artisan migrate编辑 Post 模型类以支持批量赋值:
class Post extends Model2{3 protected $fillable = ['title', 'body'];4}接下来创建文章资源控制器:
1php artisan make:controller PostController --resource编写新生成的 PostController 代码如下:
<?php2namespace App\Http\Controllers;34use Illuminate\Http\Request;56use App\Post;7use Auth;8use Session;910class PostController extends Controller {1112 public function __construct() {13 $this->middleware(['auth', 'clearance'])->except('index', 'show');14 }1516 /**17 * 显示文章列表18 *19 * @return \Illuminate\Http\Response20 */21 public function index() {22 $posts = Post::orderby('id', 'desc')->paginate(5); //show only 5 items at a time in descending order2324 return view('posts.index', compact('posts'));25 }2627 /**28 * 显示创建文章表单29 *30 * @return \Illuminate\Http\Response31 */32 public function create() {33 return view('posts.create');34 }3536 /**37 * 存储新增文章38 *39 * @param \Illuminate\Http\Request $request40 * @return \Illuminate\Http\Response41 */42 public function store(Request $request) {4344 //验证 title 和 body 字段45 $this->validate($request, [46 'title'=>'required|max:100',47 'body' =>'required',48 ]);4950 $title = $request['title'];51 $body = $request['body'];5253 $post = Post::create($request->only('title', 'body'));5455 // 基于保存结果显示成功消息56 return redirect()->route('posts.index')57 ->with('flash_message', 'Article,58 '. $post->title.' created');59 }6061 /**62 * 显示指定资源63 *64 * @param int $id65 * @return \Illuminate\Http\Response66 */67 public function show($id) {68 $post = Post::findOrFail($id); //通过 id = $id 查找文章6970 return view ('posts.show', compact('post'));71 }7273 /**74 * 显示编辑文章表单75 *76 * @param int $id77 * @return \Illuminate\Http\Response78 */79 public function edit($id) {80 $post = Post::findOrFail($id);8182 return view('posts.edit', compact('post'));83 }8485 /**86 * 更新文章87 *88 * @param \Illuminate\Http\Request $request89 * @param int $id90 * @return \Illuminate\Http\Response91 */92 public function update(Request $request, $id) {93 $this->validate($request, [94 'title'=>'required|max:100',95 'body'=>'required',96 ]);9798 $post = Post::findOrFail($id);99 $post->title = $request->input('title');100 $post->body = $request->input('body');101 $post->save();102103 return redirect()->route('posts.show',104 $post->id)->with('flash_message',105 'Article, '. $post->title.' updated');106107 }108109 /**110 * 删除文章111 *112 * @param int $id113 * @return \Illuminate\Http\Response114 */115 public function destroy($id) {116 $post = Post::findOrFail($id);117 $post->delete();118119 return redirect()->route('posts.index')120 ->with('flash_message',121 'Article successfully deleted');122123 }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 @endforeach20 </div>21 <div>22 {!! $posts->links() !!}23 </div>24 </div>25 </div>26 </div>27@endsection编写 create.blade.php 代码如下:
@extends('layouts.app')23@section('title', '| Create New Post')45@section('content')6 <div>7 <div class="col-md-8 col-md-offset-2">89 <h1>Create New Post</h1>10 <hr>1112 {{-- 使用 Laravel HTML Form Collective 创建表单 --}}13 {{ Form::open(array('route' => 'posts.store')) }}1415 <div>16 {{ Form::label('title', 'Title') }}17 {{ Form::text('title', null, array('class' => 'form-control')) }}18 <br>1920 {{ Form::label('body', 'Post Body') }}21 {{ Form::textarea('body', null, array('class' => 'form-control')) }}22 <br>2324 {{ Form::submit('Create Post', array('class' => 'btn btn-success btn-lg btn-block')) }}25 {{ Form::close() }}26 </div>27 </div>28 </div>2930@endsection编写 show.blade.php 代码如下:
@extends('layouts.app')23@section('title', '| View Post')45@section('content')67<div>89 <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 @endcan18 @can('Delete Post')19 {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}20 @endcan21 {!! Form::close() !!}2223</div>2425@endsection最后,编写 edit.blade.php 代码如下:
@extends('layouts.app')23@section('title', '| Edit Post')45@section('content')6<div>78 <div class="col-md-8 col-md-offset-2">910 <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>1617 {{ Form::label('body', 'Post Body') }}18 {{ Form::textarea('body', null, array('class' => 'form-control')) }}<br>1920 {{ Form::submit('Save', array('class' => 'btn btn-primary')) }}2122 {{ Form::close() }}23 </div>24 </div>25</div>2627@endsection完成上述工作后,再次访问项目首页,页面显示如下:

用户相关资源类
用户对应模型类和数据表已存在,所以我们直接从创建资源控制器开始:
1php artisan make:controller UserController --resource编写刚生成的 UserController 代码如下:
<?php23namespace App\Http\Controllers;45use Illuminate\Http\Request;6use App\User;7use Auth;8// 引入 laravel-permission 模型9use Spatie\Permission\Models\Role;10use Spatie\Permission\Models\Permission;11// 用于输出一次性信息12use Session;1314class UserController extends Controller {1516 public function __construct() {17 $this->middleware(['auth', 'isAdmin']); // isAdmin 中间件让具备指定权限的用户才能访问该资源18 }1920 /**21 * 显示用户列表22 *23 * @return \Illuminate\Http\Response24 */25 public function index() {26 //Get all users and pass it to the view27 $users = User::all(); 28 return view('users.index')->with('users', $users);29 }3031 /**32 * 显示创建用户角色表单33 *34 * @return \Illuminate\Http\Response35 */36 public function create() {37 // 获取所有角色并将其传递到视图38 $roles = Role::get();39 return view('users.create', ['roles'=>$roles]);40 }4142 /**43 * 在数据库中保存新创建的资源44 *45 * @param \Illuminate\Http\Request $request46 * @return \Illuminate\Http\Response47 */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 ]);5556 $user = User::create($request->only('email', 'name', 'password')); //只获取 email、name、password 字段5758 $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 user64 }65 } 66 // 重定向到 users.index 视图并显示消息67 return redirect()->route('users.index')68 ->with('flash_message',69 'User successfully added.');70 }7172 /**73 * 显示指定用户74 *75 * @param int $id76 * @return \Illuminate\Http\Response77 */78 public function show($id) {79 return redirect('users'); 80 }8182 /**83 * 显示编辑用户角色表单84 *85 * @param int $id86 * @return \Illuminate\Http\Response87 */88 public function edit($id) {89 $user = User::findOrFail($id); // 通过给定id获取用户90 $roles = Role::get(); // 获取所有角色9192 return view('users.edit', compact('user', 'roles')); // 将用户和角色数据传递到视图9394 }9596 /**97 * 更新数据库中的给定用户98 *99 * @param \Illuminate\Http\Request $request100 * @param int $id101 * @return \Illuminate\Http\Response102 */103 public function update(Request $request, $id) {104 $user = User::findOrFail($id); // 通过id获取给定角色105106 // 验证 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();115116 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 }125126 /**127 * 删除用户128 *129 * @param int $id130 * @return \Illuminate\Http\Response131 */132 public function destroy($id) {133 // 通过给定id获取并删除用户134 $user = User::findOrFail($id); 135 $user->delete();136137 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')23@section('title', '| Users')45@section('content')67<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">1314 <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>2324 <tbody>25 @foreach ($users as $user)26 <tr>2728 <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>3435 {!! Form::open(['method' => 'DELETE', 'route' => ['users.destroy', $user->id] ]) !!}36 {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}37 {!! Form::close() !!}3839 </td>40 </tr>41 @endforeach42 </tbody>4344 </table>45 </div>4647 <a href="{{ route('users.create') }}" class="btn btn-success">Add User</a>4849</div>5051@endsection编写 create.blade.php 视图代码如下:
@extends('layouts.app')23@section('title', '| Add User')45@section('content')67<div class='col-lg-4 col-lg-offset-4'>89 <h1><i class='fa fa-user-plus'></i> Add User</h1>10 <hr>1112 {{ Form::open(array('url' => 'users')) }}1314 <div>15 {{ Form::label('name', 'Name') }}16 {{ Form::text('name', '', array('class' => 'form-control')) }}17 </div>1819 <div>20 {{ Form::label('email', 'Email') }}21 {{ Form::email('email', '', array('class' => 'form-control')) }}22 </div>2324 <div>25 @foreach ($roles as $role)26 {{ Form::checkbox('roles[]', $role->id ) }}27 {{ Form::label($role->name, ucfirst($role->name)) }}<br>2829 @endforeach30 </div>3132 <div>33 {{ Form::label('password', 'Password') }}<br>34 {{ Form::password('password', array('class' => 'form-control')) }}3536 </div>3738 <div>39 {{ Form::label('password', 'Confirm Password') }}<br>40 {{ Form::password('password_confirmation', array('class' => 'form-control')) }}4142 </div>4344 {{ Form::submit('Add', array('class' => 'btn btn-primary')) }}4546 {{ Form::close() }}4748</div>4950@endsection编写 edit.blade.php 视图代码如下:
@extends('layouts.app')23@section('title', '| Edit User')45@section('content')67<div class='col-lg-4 col-lg-offset-4'>89 <h1><i class='fa fa-user-plus'></i> Edit {{$user->name}}</h1>10 <hr>1112 {{ Form::model($user, array('route' => array('users.update', $user->id), 'method' => 'PUT')) }}{{-- Form model binding to automatically populate our fields with user data --}}1314 <div>15 {{ Form::label('name', 'Name') }}16 {{ Form::text('name', null, array('class' => 'form-control')) }}17 </div>1819 <div>20 {{ Form::label('email', 'Email') }}21 {{ Form::email('email', null, array('class' => 'form-control')) }}22 </div>2324 <h5><b>Give Role</b></h5>2526 <div>27 @foreach ($roles as $role)28 {{ Form::checkbox('roles[]', $role->id, $user->roles ) }}29 {{ Form::label($role->name, ucfirst($role->name)) }}<br>3031 @endforeach32 </div>3334 <div>35 {{ Form::label('password', 'Password') }}<br>36 {{ Form::password('password', array('class' => 'form-control')) }}3738 </div>3940 <div>41 {{ Form::label('password', 'Confirm Password') }}<br>42 {{ Form::password('password_confirmation', array('class' => 'form-control')) }}4344 </div>4546 {{ Form::submit('Add', array('class' => 'btn btn-primary')) }}4748 {{ Form::close() }}4950</div>5152@endsection至此,用户相关控制器和视图已编写完毕,下面来看权限相关资源类。
权限相关资源类
权限对应模型和数据表也已经存在了,所以只需通过以下命令创建权限资源控制器:
1php artisan make:controller PermissionController --resource编写 PermissionController 控制器代码如下:
<?php23namespace App\Http\Controllers;45use Illuminate\Http\Request;67use Auth;89// 引入 laravel-permission 模型10use Spatie\Permission\Models\Role;11use Spatie\Permission\Models\Permission;1213use Session;1415class PermissionController extends Controller {1617 public function __construct() {18 $this->middleware(['auth', 'isAdmin']); // isAdmin 中间件让具备指定权限的用户才能访问该资源1920 }2122 /**23 * 显示权限列表24 *25 * @return \Illuminate\Http\Response26 */27 public function index() {28 $permissions = Permission::all(); // 获取所有权限2930 return view('permissions.index')->with('permissions', $permissions);31 }3233 /**34 * 显示创建权限表单35 *36 * @return \Illuminate\Http\Response37 */38 public function create() {39 $roles = Role::get(); // 获取所有角色4041 return view('permissions.create')->with('roles', $roles);42 }4344 /**45 * 保存新创建的权限46 *47 * @param \Illuminate\Http\Request $request48 * @return \Illuminate\Http\Response49 */50 public function store(Request $request) {51 $this->validate($request, [52 'name'=>'required|max:40',53 ]);5455 $name = $request['name'];56 $permission = new Permission();57 $permission->name = $name;5859 $roles = $request['roles'];6061 $permission->save();6263 if (!empty($request['roles'])) { // 如果选择了角色64 foreach ($roles as $role) {65 $r = Role::where('id', '=', $role)->firstOrFail(); // 将输入角色和数据库记录进行匹配6667 $permission = Permission::where('name', '=', $name)->first(); // 将输入权限与数据库记录进行匹配68 $r->givePermissionTo($permission);69 }70 }7172 return redirect()->route('permissions.index')73 ->with('flash_message',74 'Permission'. $permission->name.' added!');7576 }7778 /**79 * 显示给定权限80 *81 * @param int $id82 * @return \Illuminate\Http\Response83 */84 public function show($id) {85 return redirect('permissions');86 }8788 /**89 * 显示编辑权限表单90 *91 * @param int $id92 * @return \Illuminate\Http\Response93 */94 public function edit($id) {95 $permission = Permission::findOrFail($id);9697 return view('permissions.edit', compact('permission'));98 }99100 /**101 * 更新指定权限102 *103 * @param \Illuminate\Http\Request $request104 * @param int $id105 * @return \Illuminate\Http\Response106 */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();114115 return redirect()->route('permissions.index')116 ->with('flash_message',117 'Permission'. $permission->name.' updated!');118119 }120121 /**122 * 删除给定权限123 *124 * @param int $id125 * @return \Illuminate\Http\Response126 */127 public function destroy($id) {128 $permission = Permission::findOrFail($id);129130 // 让特定权限无法删除 131 if ($permission->name == "Administer roles & permissions") {132 return redirect()->route('permissions.index')133 ->with('flash_message',134 'Cannot delete this Permission!');135 }136137 $permission->delete();138139 return redirect()->route('permissions.index')140 ->with('flash_message',141 'Permission deleted!');142143 }144}注册相应路由到 app/routes/web.php:
Route::resource('permissions', 'PermissionController');根据 PermissionController 控制器提供的方法,需要创建三个对应视图文件。
首先创建 resources/views/permissions/index.blade.php 文件:
@extends('layouts.app')23@section('title', '| Permissions')45@section('content')67<div class="col-lg-10 col-lg-offset-1">8 <h1><i class="fa fa-key"></i>Available Permissions910 <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">1516 <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>2829 {!! Form::open(['method' => 'DELETE', 'route' => ['permissions.destroy', $permission->id] ]) !!}30 {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}31 {!! Form::close() !!}3233 </td>34 </tr>35 @endforeach36 </tbody>37 </table>38 </div>3940 <a href="{{ URL::to('permissions/create') }}" class="btn btn-success">Add Permission</a>4142</div>4344@endsection接下来创建 resources/views/permissions/create.blade.php 视图文件:
@extends('layouts.app')23@section('title', '| Create Permission')45@section('content')67<div class='col-lg-4 col-lg-offset-4'>89 <h1><i class='fa fa-key'></i> Add Permission</h1>10 <br>1112 {{ Form::open(array('url' => 'permissions')) }}1314 <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 yet19 <h4>Assign Permission to Roles</h4>2021 @foreach ($roles as $role) 22 {{ Form::checkbox('roles[]', $role->id ) }}23 {{ Form::label($role->name, ucfirst($role->name)) }}<br>2425 @endforeach26 @endif27 <br>28 {{ Form::submit('Add', array('class' => 'btn btn-primary')) }}2930 {{ Form::close() }}3132</div>3334@endsection最后创建 resources/views/permissions/edit.blade.php 视图文件:
@extends('layouts.app')23@section('title', '| Edit Permission')45@section('content')67<div class='col-lg-4 col-lg-offset-4'>89 <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 --}}1213 <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')) }}1920 {{ Form::close() }}2122</div>2324@endsection最后创建角色相关资源类。
角色相关资源类
和权限一样,对应模型类和数据表已经存在,所以也是从创建资源控制器开始:
1php artisan make:controller RoleController --resource编写刚生成的 RoleController 控制器代码如下:
<?php23namespace App\Http\Controllers;45use Illuminate\Http\Request;6use Auth;7// 引入 laravel-permission 模型8use Spatie\Permission\Models\Role;9use Spatie\Permission\Models\Permission;10use Session;1112class RoleController extends Controller {1314 public function __construct() {15 $this->middleware(['auth', 'isAdmin']); // isAdmin 中间件让具备指定权限的用户才能访问该资源1617 }1819 /**20 * 显示角色列表21 *22 * @return \Illuminate\Http\Response23 */24 public function index() {25 $roles = Role::all();// 获取所有角色2627 return view('roles.index')->with('roles', $roles);28 }2930 /**31 * 显示创建角色表单32 *33 * @return \Illuminate\Http\Response34 */35 public function create() {36 $permissions = Permission::all();// 获取所有权限3738 return view('roles.create', ['permissions'=>$permissions]);39 }4041 /**42 * 保存新创建的角色43 *44 * @param \Illuminate\Http\Request $request45 * @return \Illuminate\Http\Response46 */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 );5455 $name = $request['name'];56 $role = new Role();57 $role->name = $name;5859 $permissions = $request['permissions'];6061 $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 }6970 return redirect()->route('roles.index')71 ->with('flash_message',72 'Role'. $role->name.' added!'); 73 }7475 /**76 * 显示指定角色77 *78 * @param int $id79 * @return \Illuminate\Http\Response80 */81 public function show($id) {82 return redirect('roles');83 }8485 /**86 * 显示编辑角色表单87 *88 * @param int $id89 * @return \Illuminate\Http\Response90 */91 public function edit($id) {92 $role = Role::findOrFail($id);93 $permissions = Permission::all();9495 return view('roles.edit', compact('role', 'permissions'));96 }9798 /**99 * 更新角色100 *101 * @param \Illuminate\Http\Request $request102 * @param int $id103 * @return \Illuminate\Http\Response104 */105 public function update(Request $request, $id) {106107 $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 ]);113114 $input = $request->except(['permissions']);115 $permissions = $request['permissions'];116 $role->fill($input)->save();117118 $p_all = Permission::all();//获取所有权限119120 foreach ($p_all as $p) {121 $role->revokePermissionTo($p); // 移除与角色关联的所有权限 122 }123124 foreach ($permissions as $permission) {125 $p = Permission::where('id', '=', $permission)->firstOrFail(); //从数据库中获取相应权限126 $role->givePermissionTo($p); // 分配权限到角色127 }128129 return redirect()->route('roles.index')130 ->with('flash_message',131 'Role'. $role->name.' updated!');132 }133134 /**135 * 删除指定权限136 *137 * @param int $id138 * @return \Illuminate\Http\Response139 */140 public function destroy($id)141 {142 $role = Role::findOrFail($id);143 $role->delete();144145 return redirect()->route('roles.index')146 ->with('flash_message',147 'Role deleted!');148149 }150}注册相应路由到 app/routes/web.php:
Route::resource('roles', 'RoleController');根据上面的 RoleController 控制器,需要创建三个相应的视图文件。
首先创建 resources/views/roles/index.blade.php 视图文件:
@extends('layouts.app')23@section('title', '| Roles')45@section('content')67<div class="col-lg-10 col-lg-offset-1">8 <h1><i class="fa fa-key"></i> Roles910 <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>2223 <tbody>24 @foreach ($roles as $role)25 <tr>2627 <td>{{ $role->name }}</td>2829 <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>3233 {!! Form::open(['method' => 'DELETE', 'route' => ['roles.destroy', $role->id] ]) !!}34 {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}35 {!! Form::close() !!}3637 </td>38 </tr>39 @endforeach40 </tbody>4142 </table>43 </div>4445 <a href="{{ URL::to('roles/create') }}" class="btn btn-success">Add Role</a>4647</div>4849@endsection然后创建 resources/views/roles/create.blade.php 视图文件:
@extends('layouts.app')23@section('title', '| Add Role')45@section('content')67<div class='col-lg-4 col-lg-offset-4'>89 <h1><i class='fa fa-key'></i> Add Role</h1>10 <hr>1112 {{ Form::open(array('url' => 'roles')) }}1314 <div>15 {{ Form::label('name', 'Name') }}16 {{ Form::text('name', null, array('class' => 'form-control')) }}17 </div>1819 <h5><b>Assign Permissions</b></h5>2021 <div>22 @foreach ($permissions as $permission)23 {{ Form::checkbox('permissions[]', $permission->id ) }}24 {{ Form::label($permission->name, ucfirst($permission->name)) }}<br>2526 @endforeach27 </div>2829 {{ Form::submit('Add', array('class' => 'btn btn-primary')) }}3031 {{ Form::close() }}3233</div>3435@endsection最后创建 resources/views/roles/edit.blade.php 视图文件:
@extends('layouts.app')23@section('title', '| Edit Role')45@section('content')67<div class='col-lg-4 col-lg-offset-4'>8 <h1><i class='fa fa-key'></i> Edit Role: {{$role->name}}</h1>9 <hr>1011 {{ Form::model($role, array('route' => array('roles.update', $role->id), 'method' => 'PUT')) }}1213 <div>14 {{ Form::label('name', 'Role Name') }}15 {{ Form::text('name', null, array('class' => 'form-control')) }}16 </div>1718 <h5><b>Assign Permissions</b></h5>19 @foreach ($permissions as $permission)2021 {{Form::checkbox('permissions[]', $permission->id, $role->permissions ) }}22 {{Form::label($permission->name, ucfirst($permission->name)) }}<br>2324 @endforeach25 <br>26 {{ Form::submit('Edit', array('class' => 'btn btn-primary')) }}2728 {{ Form::close() }} 29</div>3031@endsection权限中间件
到这里还没有结束,我们在上面的控制器中有用到 isAdmin 和 clearance 中间件,下面需要来创建并注册这两个中间件。
首先创建 AdminMiddleware 中间件:
php artisan make:middleware AdminMiddleware编写 AdminMiddleware 中间件代码如下:
<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Support\Facades\Auth;7use App\User;89class AdminMiddleware10{11 /**12 * Handle an incoming request.13 *14 * @param \Illuminate\Http\Request $request15 * @param \Closure $next16 * @return mixed17 */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 }2728 return $next($request);29 }30}该中间件的作用主要是用于判断指定用户是否具备管理员权限。通过上面的代码,可以看出系统的第一个用户默认是管理员,以防止第一个用户出现权限死锁的情况。
接下来创建另一个中间件 ClearanceMiddleware:
php artisan make:middleware ClearanceMiddleware编写 ClearanceMiddleware 代码如下:
<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Support\Facades\Auth;78class ClearanceMiddleware {9 /**10 * Handle an incoming request.11 *12 * @param \Illuminate\Http\Request $request13 * @param \Closure $next14 * @return mixed15 */16 public function handle($request, Closure $next) {17 if (Auth::user()->hasPermissionTo('Administer roles & permissions')) 18 {19 return $next($request); // 管理员具备所有权限20 }2122 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 }3233 if ($request->is('posts/*/edit')) // 文章编辑权限34 {35 if (!Auth::user()->hasPermissionTo('Edit Post')) {36 abort('401');37 } else {38 return $next($request);39 }40 }4142 if ($request->isMethod('Delete')) // 文章删除权限43 {44 if (!Auth::user()->hasPermissionTo('Delete Post')) {45 abort('401');46 }47 else48 {49 return $next($request);50 }51 }5253 return $next($request);54 }55}该中间件的主要作用是判断用户是否具备给定操作的权限。
将上述两个中间件注册到 app/Http/kernel.php 的 $routeMiddleware 属性中:
protected $routeMiddleware = [2 ... // 其他中间件3 'isAdmin' => AdminMiddleware::class,4 'clearance' => ClearanceMiddleware::class5];最后我们为 401 状态码编写一个错误页面 resources\views\errors\401.blade.php:
@extends('layouts.app')23@section('content')4 <div class='col-lg-4 col-lg-offset-4'>5 <h1><center>401<br>6 ACCESS DENIED</center></h1>7 </div>89@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这个路由权限都是写死的,不方便管理啊,如果创建了一个新角色且不是要重新修改路由权限了