> 除了内置提供的 用户认证 服务外,Laravel 还提供一种更简单的方式来处理用户授权动作。类似用户认证,Laravel 有 2 种主要方式来实现用户授权:gates 和策略。 可以把 gates 和策略类比于路由和控制器。Gates 提供了一个简单、基于闭包的方式来授权认证。策略则和控制器类似,在特定的模型或者资源中通过分组来实现授权认证的逻辑。我们先来看看 gates,然后再看策略。 在你的应用中,不要将 gates 和策略当作相互排斥的方式。大部分应用很可能同时包含 gates 和策略,并且能很好的工作。Gates 大部分应用在模型和资源无关的地方,比如查看管理员的面板。与此相比,策略应该用在特定的模型或者资源中。 > 本篇文章主要讲解利用 Laravel 的用户授权机制,实现用户角色和权限的控制 ### 创建 users_role 表 进入项目根目录,执行 `php artisan make:migration create_permissions_and_roles_table` 代码: increments('id'); $table->string('name'); $table->string('description')->nullable(); $table->timestamps(); }); Schema::create('users_permission', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->timestamps(); }); Schema::create('users_permission_role', function (Blueprint $table) { $table->integer('permission_id')->unsigned(); $table->integer('role_id')->unsigned(); $table->foreign('permission_id') ->references('id') ->on('permissions') ->onDelete('cascade'); $table->foreign('role_id') ->references('id') ->on('roles') ->onDelete('cascade'); $table->primary(['permission_id', 'role_id']); }); Schema::table('users', function (Blueprint $table) { $table->integer('role_id')->unsigned()->default(0); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users_role'); Schema::dropIfExists('users_permission'); Schema::dropIfExists('users_permission_role'); Schema::table('users', function ($table) { $table->dropColumn('role_id'); }); } } ?> ### RoleController with([ 'roles' => Role::latest()->get(), ]); } public function create() { return view('admin/role/create'); } public function store(Requests\StoreRoleRequest $request) { $input = $request->all(); $role = Role::create($input); if ($role) { return redirect('admin/role')->withSuccess('创建成功!'); } else { return redirect()->back()->withInput()->withWarning('创建失败!'); } } public function edit($id) { return view('admin/role/edit')->with([ 'roles' => Role::findOrFail($id), ]); } public function update(Requests\StoreRoleRequest $request, $id) { $role = Role::findOrFail($id); $result = $role->update($request->except('id')); if ($result) { return redirect('admin/role')->withSuccess('更新成功!'); } else { return redirect()->back()->withInput()->withWarning('更新失败!'); } } public function destroy($id) { Role::find($id)->delRole(); return redirect()->back()->withSuccess('删除成功!'); } public function authority($id) { return view('admin/role/authority')->with([ 'roles' => Role::findOrFail($id), 'permissions' => Permission::getRoutes(), ]); } public function updauthority(Request $request) { $role = Role::findOrFail($request->get('role_id')); $role->givePermissionTo($request->get('name')); return redirect('admin/role')->withSuccess('更新成功!'); } } ?> ### Role.php where('id', '!=', '1')->orderBy('id', 'desc'); } /** * 多对多 -- 角色/权限 * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function permissions() { return $this->belongsToMany(Permission::class, 'users_permission_role'); } /** * 给角色添加权限 * @param array $aPermission */ public function givePermissionTo($aPermission) { //根据当前角色,删除取消的权限,关联关系通过外键自动删除 $this->permissions()->whereNotIn('name', $aPermission)->delete(); //重新生成关联数据 foreach ($aPermission as $permission) { if($this->permissions()->where('name', $permission)->get()->isEmpty()) { //创建权限和关联关系 $this->permissions()->save( Permission::create(array('name'=>$permission)) ); } } } /** * 删除角色,并删除对应权限,关联关系由外键触发删除 */ public function delRole() { $permissions = $this->permissions()->get(); if($permissions) { foreach ($permissions as $item) { $item->delete(); } } $this->delete(); User::where('role_id', $this->id)->update(['role_id' => 2]); } } ?> ### Permission.php belongsToMany(Role::class, 'users_permission_role'); } /** * 根据路由获取后台的控制器 * @return Ambigous */ public static function getRoutes() { $routes = array(); $routesAction = Route::getRoutes(); foreach ($routesAction as $key=>$value) { $action_name = $value->getActionName(); if(is_null($value) || !str_contains($action_name, 'Admin')) { continue; } $action = str_replace('App\Http\Controllers\Admin\\', '', $action_name); list($controller, $function) = explode('@', $action); $routes[$controller][] = array( 'action' => $action, 'controller' => $controller, 'function' => $function, 'methods' => $value->getMethods()[0], 'name' => str_replace('controllerat', '-', str_slug($action)), ); } return $routes; } } ?> ### 在需要验证的controller中添加如下代码 public function __construct() { //用户权限验证 $this->middleware(function ($request, $next) { //普通用户不通过 if(Auth::user()->hasRole('member')) { return redirect('/'); } if(!AuthService::checkauthor(Auth::user())) { abort('403'); } return $next($request); }); } ### 注册策略 >一旦该授权策略存在,需要将它进行注册。AuthServiceProvider 包含了一个 policies 属性,可将各种模型对应至管理它们的授权策略。注册一个策略将引导 Laravel 在授权动作访问给定模型时使用何种策略: 在app/Providers/AuthServiceProvider.php 添加如下代码 'App\Policies\ModelPolicy', ]; /** * Register any authentication / authorization services. * * @return void */ public function boot(GateContract $gate, User $user) { $this->registerPolicies(); //parent::registerPolicies($gate); Gate::before(function($user) { if (!$user->hasRole('LeadDeveloper') && !$user->hasRole('member')) { $permissions = \App\Models\Permission::with('roles')->get(); foreach ($permissions as $permission) { Gate::define($permission->name, function($user) use ($permission) { return $user->hasPermission($permission); }); } } else { return true; } }); } } ?> ### 使用策略授权动作# ##### 通过用户模型# Laravel 应用内置的 User 模型包含 2 个有用的方法来授权动作:can 和 cant。can 方法指定需要授权的动作和相关的模型。例如,判定一个用户是否授权更新给定的 Post 模型: if ($user->can('update', $post)) { // } 如果给定模型的 策略已被注册,can 方法会自动调用核实的策略方法并且返回 boolean 值。如果没有策略注册到这个模型,can 方法会尝试调用和动作名相匹配的基于闭包的 Gate。 不需要指定模型的动作# 一些动作,比如 create,并不需要指定模型实例。在这种情况下,可传递一个类名给 can 方法。当授权动作时,这个类名将被用来判断使用哪个策略: use App\Post; if ($user->can('create', Post::class)) { // 执行相关策略中的「create」方法... } ##### 通过中间件# Laravel 包含一个可以在请求到达路由或控制器之前就进行动作授权的中间件。默认,Illuminate\Auth\Middleware\Authorize 中间件被指定到 App\Http\Kernel 类中 can 键上。我们用一个授权用户更新博客的例子来讲解 can 中间件的使用: use App\Post; Route::put('/post/{post}', function (Post $post) { // 当前用户可以更新博客... })->middleware('can:update,post'); 在这个例子中,我们传递给 can 中间件 2 个参数。第一个是需要授权的动作的名称,第二个是我们希望传递给策略方法的路由参数。这里因为使用了 隐式模型绑定,一个 Post 会被传递给策略方法。如果用户不被授权访问指定的动作,这个中间件会生成带有 403 状态码的 HTTP 响应。 不需要指定模型的动作# 同样的,一些动作,比如 create,并不需要指定模型实例。在这种情况下,可传递一个类名给中间件。当授权动作时,这个类名将被用来判断使用哪个策略: Route::post('/post', function () { // 当前用户可以创建博客... })->middleware('can:create,App\Post'); ##### 通过控制器辅助函数# 除了在 User 模型中提供辅助方法外,Laravel 也为所有继承了 App\Http\Controllers\Controller 基类的控制器提供了一个有用的 authorize 方法。和 can 方法类似,这个方法接收需要授权的动作和相关的模型作为参数。如果动作不被授权,authorize 方法会抛出 Illuminate\Auth\Access\AuthorizationException 异常,然后被 Laravel 默认的异常处理器转化为带有 403 状态码的 HTTP 响应: authorize('update', $post); // 当前用户可以更新博客... } } 不需要指定模型的动作# 和之前讨论的一样,一些动作,比如 create,并不需要指定模型实例。在这种情况下,可传递一个类名给 authorize 方法。当授权动作时,这个类名将被用来判断使用哪个策略: /** * 新建博客 * * @param Request $request * @return Response */ public function create(Request $request) { $this->authorize('create', Post::class); // 当前用户可以新建博客... } ##### 通过 Blade 模板# 当编写 Blade 模板时,你可能希望页面的指定部分只展示给允许授权访问给定动作的用户。例如,你可能希望只展示更新表单给有权更新博客的用户。这种情况下,你可以直接使用 @can 和 @cannot 指令。 @can('update', $post) @endcan @cannot('update', $post) @endcannot 这些指令在编写 @if 和 @unless 时提供了方便的缩写。@can 和 @cannot 各自转化为如下声明: @if (Auth::user()->can('update', $post)) @endif @unless (Auth::user()->can('update', $post)) @endunless 不需要指定模型的动作# 和大部分其他的授权方法类似,当动作不需要模型实例时,你可以传递一个类名给 @can 和 @cannot 指令: @can('create', Post::class) @endcan @cannot('create', Post::class) @endcannot 建议查看本篇文章之前,先查看 Laravel 的 [用户授权](http://d.laravel-china.org/docs/5.3/authorization "用户授权")