认证与授权的区别
认证(Authentication) 用来确认用户「是谁」。登录处理就是典型例子。
授权(Authorization) 用来判断该用户「能做什么」。例如只能编辑自己发的帖子、只有管理员能修改设置等。
在通过认证 实现登录功能后,下一步就是授权。
Laravel 的授权功能提供 Gate 与 Policy 两种方式。选择哪种取决于用途。
选择依据
场景 推荐 与具体模型无关的简单判断 Gate 针对模型的 CRUD 权限管理 Policy 管理后台访问控制 Gate 博客文章的发布、编辑、删除权限 Policy
Gate
Gate 是基于闭包的简单授权检查,适合与特定模型无关的权限判断。
Gate 的授权流程
定义 Gate
Gate 定义在 App\Providers\AppServiceProvider 的 boot 方法中,使用 Gate::define()。
// app/Providers/AppServiceProvider.php
use App\Models\ Post ;
use App\Models\ User ;
use Illuminate\Support\Facades\ Gate ;
public function boot () : void
{
Gate :: define ( 'update-post' , function ( User $user , Post $post ) {
return $user -> id === $post -> user_id ;
});
}
Gate 闭包的第一个参数总是当前已认证用户。第二个及以后的参数可以传入目标模型等额外信息。
使用 Gate 检查权限
在控制器中使用 Gate 检查权限可用 Gate::allows() 或 Gate::denies()。
use Illuminate\Support\Facades\ Gate ;
public function update ( Request $request , Post $post ) : RedirectResponse
{
if ( ! Gate :: allows ( 'update-post' , $post )) {
abort ( 403 );
}
// 更新文章的处理...
return redirect ( '/posts' );
}
使用 Gate::authorize() 抛出异常
无权限时自动返回 403 响应可以使用 Gate::authorize(),省去手写 abort(403)。
public function update ( Request $request , Post $post ) : RedirectResponse
{
Gate :: authorize ( 'update-post' , $post );
// 有权限时到达此处
// 更新文章的处理...
return redirect ( '/posts' );
}
当权限不足时,Gate::authorize() 会抛出 Illuminate\Auth\Access\AuthorizationException。Laravel 会自动将其转换为 403 响应。
管理员绕过(before 方法)
若想给管理员用户所有权限,可以使用 Gate::before()。
use App\Models\ User ;
use Illuminate\Support\Facades\ Gate ;
public function boot () : void
{
// $ability 是正在尝试的 gate 名(例如 'update-post')
Gate :: before ( function ( User $user , string $ability ) {
if ( $user -> isAdministrator ()) {
return true ;
}
});
Gate :: define ( 'update-post' , function ( User $user , Post $post ) {
return $user -> id === $post -> user_id ;
});
}
若 before 闭包返回非 null 值,将其作为最终结果。返回 null 或无返回值时会继续执行正常的 Gate 判定。
Blade 中的 @can / @cannot
在模板中使用 Gate,可用 @can 与 @cannot 指令:
@can ( 'update-post' , $post )
< a href = " {{ route ('posts.edit', $post ) }} " > 编辑 </ a >
@endcan
@cannot ( 'update-post' , $post )
< p > 你无权编辑该文章。 </ p >
@endcannot
Policy
Policy 将某模型相关的授权逻辑归入一个类。对 Post 模型的创建、查看、编辑、删除等权限,用 Policy 比 Gate 更合适。
Policy 的授权流程
生成 Policy
使用 make:policy Artisan 命令生成 Policy 类。
php artisan make:policy PostPolicy
若想生成包含对应模型 CRUD 方法的完整骨架,使用 --model:
php artisan make:policy PostPolicy --model=Post
会生成 app/Policies/PostPolicy.php。
模型与 Policy 的自动识别
Laravel 默认根据命名约定自动识别 Policy:
模型:app/Models/Post.php
Policy:app/Policies/PostPolicy.php
只要遵循命名约定,就无需手动注册。
若不遵循命名约定或希望手动注册,可在 AppServiceProvider 的 boot 中使用 Gate::policy(): use App\Models\ Post ;
use App\Policies\ PostPolicy ;
use Illuminate\Support\Facades\ Gate ;
Gate :: policy ( Post :: class , PostPolicy :: class );
在 Laravel 13 中,也可以通过 #[UsePolicy] 属性在模型上进行声明式注册。 use App\Policies\ PostPolicy ;
use Illuminate\Database\Eloquent\Attributes\ UsePolicy ;
#[ UsePolicy ( PostPolicy :: class )]
class Post extends Model {}
实现 Policy 方法
通过 --model 生成的 Policy 已经带有标准 CRUD 方法。
<? php
namespace App\Policies ;
use App\Models\ Post ;
use App\Models\ User ;
class PostPolicy
{
/** 是否可查看列表 */
public function viewAny ( User $user ) : bool
{
return true ;
}
/** 是否可查看某一条 */
public function view ( User $user , Post $post ) : bool
{
return true ;
}
/** 是否可创建 */
public function create ( User $user ) : bool
{
return true ;
}
/** 是否可更新 */
public function update ( User $user , Post $post ) : bool
{
return $user -> id === $post -> user_id ;
}
/** 是否可删除 */
public function delete ( User $user , Post $post ) : bool
{
return $user -> id === $post -> user_id ;
}
}
管理员绕过(before 方法)
Policy 中也可通过 before 给管理员所有权限:
/**
* 在其他 policy 方法之前调用
* $ability 为 policy 方法名('update'、'delete' 等)
*/
public function before ( User $user , string $ability ) : bool | null
{
if ( $user -> isAdministrator ()) {
return true ;
}
return null ; // 返回 null 则继续正常 policy 判断
}
before 只会在 policy 类中存在对应方法时才会被调用。例如 update 方法不存在,before 就不会被调用。
before 回调的执行顺序
在控制器中使用 Policy
authorize() 方法
Laravel 控制器提供了 authorize() 帮助方法(若未使用基础控制器,可使用 Gate::authorize())。
Gate::authorize()
User::can() / cannot()
<? php
namespace App\Http\Controllers ;
use App\Models\ Post ;
use Illuminate\Http\ RedirectResponse ;
use Illuminate\Http\ Request ;
use Illuminate\Support\Facades\ Gate ;
class PostController extends Controller
{
public function update ( Request $request , Post $post ) : RedirectResponse
{
Gate :: authorize ( 'update' , $post );
// 更新文章的处理...
return redirect () -> route ( 'posts.index' );
}
public function store ( Request $request ) : RedirectResponse
{
Gate :: authorize ( 'create' , Post :: class );
// 创建文章的处理...
return redirect () -> route ( 'posts.index' );
}
public function destroy ( Post $post ) : RedirectResponse
{
Gate :: authorize ( 'delete' , $post );
$post -> delete ();
return redirect () -> route ( 'posts.index' );
}
}
<? php
namespace App\Http\Controllers ;
use App\Models\ Post ;
use Illuminate\Http\ RedirectResponse ;
use Illuminate\Http\ Request ;
class PostController extends Controller
{
public function update ( Request $request , Post $post ) : RedirectResponse
{
if ( $request -> user () -> cannot ( 'update' , $post )) {
abort ( 403 );
}
// 更新文章的处理...
return redirect () -> route ( 'posts.index' );
}
}
使用 authorizeResource() 一次注册 RESTful Policy
在构造器中调用 authorizeResource(),可以将控制器各个 action 自动映射到对应 policy 方法。
<? php
namespace App\Http\Controllers ;
use App\Models\ Post ;
class PostController extends Controller
{
public function __construct ()
{
$this -> authorizeResource ( Post :: class , 'post' );
}
// index()、show()、create()、store()、edit()、update()、destroy() 会自动应用对应 policy 方法
}
action 与 policy 方法的对应关系:
控制器 action policy 方法 indexviewAnyshowviewcreate / storecreateedit / updateupdatedestroydelete
使用 authorizeResource() 可以省去在每个 action 单独调用 authorize()。与 RESTful 资源控制器配合尤其方便。
在中间件中授权
在路由级做授权检查,使用 can 中间件。
use App\Models\ Post ;
// 只允许具备更新权限的用户访问
Route :: put ( '/posts/{post}' , [ PostController :: class , 'update' ])
-> middleware ( 'can:update,post' );
// 只允许具备创建权限的用户访问
Route :: post ( '/posts' , [ PostController :: class , 'store' ])
-> middleware ( 'can:create,App\Models\Post' );
也可以用 can 方法写得更简洁:
Route :: put ( '/posts/{post}' , [ PostController :: class , 'update' ])
-> can ( 'update' , 'post' );
在 Blade 中使用 Policy
注册好 Policy 之后,Blade 的 @can / @cannot 指令也会自动使用 Policy。
{{-- 只对有权限的用户显示编辑按钮 --}}
@can ( 'update' , $post )
< a href = " {{ route ('posts.edit', $post ) }} " class = "btn" > 编辑 </ a >
@endcan
{{-- 删除按钮同理 --}}
@can ( 'delete' , $post )
< form method = "POST" action = " {{ route ('posts.destroy', $post ) }} " >
@csrf
@method ( ' DELETE ' )
< button type = "submit" > 删除 </ button >
</ form >
@endcan
{{-- 新建按钮 --}}
@can ( 'create' , App \ Models \ Post :: class )
< a href = " {{ route ('posts.create') }} " > 新建文章 </ a >
@endcan
实用示例:博客文章权限管理
在博客应用中实现「作者才能编辑或删除自己的文章」:
生成 Policy
php artisan make:policy PostPolicy --model=Post
实现 Policy 方法
<? php
namespace App\Policies ;
use App\Models\ Post ;
use App\Models\ User ;
class PostPolicy
{
/** 给管理员所有权限 */
public function before ( User $user , string $ability ) : bool | null
{
if ( $user -> is_admin ) {
return true ;
}
return null ;
}
public function viewAny ( User $user ) : bool
{
return true ;
}
public function view ( User $user , Post $post ) : bool
{
return true ;
}
public function create ( User $user ) : bool
{
return true ;
}
public function update ( User $user , Post $post ) : bool
{
return $user -> id === $post -> user_id ;
}
public function delete ( User $user , Post $post ) : bool
{
return $user -> id === $post -> user_id ;
}
}
在控制器中添加 authorizeResource()
<? php
namespace App\Http\Controllers ;
use App\Models\ Post ;
use Illuminate\Http\ RedirectResponse ;
use Illuminate\Http\ Request ;
use Illuminate\View\ View ;
class PostController extends Controller
{
public function __construct ()
{
$this -> authorizeResource ( Post :: class , 'post' );
}
public function index () : View
{
$posts = Post :: latest () -> paginate ( 10 );
return view ( 'posts.index' , compact ( 'posts' ));
}
public function store ( Request $request ) : RedirectResponse
{
$post = $request -> user () -> posts () -> create (
$request -> validate ([
'title' => [ 'required' , 'string' , 'max:255' ],
'body' => [ 'required' , 'string' ],
])
);
return redirect () -> route ( 'posts.show' , $post );
}
public function update ( Request $request , Post $post ) : RedirectResponse
{
$post -> update (
$request -> validate ([
'title' => [ 'required' , 'string' , 'max:255' ],
'body' => [ 'required' , 'string' ],
])
);
return redirect () -> route ( 'posts.show' , $post );
}
public function destroy ( Post $post ) : RedirectResponse
{
$post -> delete ();
return redirect () -> route ( 'posts.index' );
}
}
在 Blade 模板中添加权限判断
{{-- resources/views/posts/show.blade.php --}}
< h1 > {{ $post -> title }} </ h1 >
< p > {{ $post -> body }} </ p >
< p > 作者: {{ $post -> user -> name }} </ p >
@can ( 'update' , $post )
< a href = " {{ route ('posts.edit', $post ) }} " > 编辑 </ a >
@endcan
@can ( 'delete' , $post )
< form method = "POST" action = " {{ route ('posts.destroy', $post ) }} " >
@csrf
@method ( ' DELETE ' )
< button type = "submit" > 删除 </ button >
</ form >
@endcan
Gate :与特定模型无关的简单权限判断。例如管理后台访问、全局设置修改权限等。
Policy :针对模型的 CRUD 权限。为 Post、Order、Comment 等资源分别建立 policy 类。
// 通过 Gate 判定
Gate :: allows ( 'update-post' , $post ); // true/false
Gate :: denies ( 'update-post' , $post ); // true/false
Gate :: authorize ( 'update-post' , $post ); // 失败时抛异常
// 通过用户模型判定
$user -> can ( 'update' , $post ); // true/false
$user -> cannot ( 'update' , $post ); // true/false
// 控制器中
Gate :: authorize ( 'update' , $post ); // 失败时 403
// Blade 中
@ can ( 'update' , $post ) ... @ endcan
@ cannot ( 'update' , $post ) ... @ endcannot
# 生成空 policy
php artisan make:policy PostPolicy
# 生成与模型对应的 CRUD 方法的 policy
php artisan make:policy PostPolicy --model=Post