laravel5.5授權系統
[toc]
背景:
帖子屬於某個人(擁有屬性user_id),如果這是個私密帖子,只有自己才可以看到,傳統的做法是
class PostsController extends Controller { public function show($id) { $post = Post::findOrFail($id); //只有登錄的用戶id和帖子的所屬user_id相同才可以通過 if (auth()->id() != $post->user_id) { // 403 權限錯誤 abort(403, 'Sorry, not sorrry.'); } return $post->title; } }
以上做法問題不大,但是有沒有更優雅的做法呢,今天要講的就是laravel的用戶授權,Laravel 有 2 種主要方式來實現用戶授權:gates 和策略。
1. Gates
1.1 一個簡單的使用Gates的例子
- 準備工作:
創建遷移文件
php artisan make:migration create_post_table --create=posts
文件內容
Schema::create('posts', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id')->unsigned()->index(); $table->string('title'); $table->text('body'); $table->timestamps(); }); \DB::table('posts')->insert( array( 'user_id' => 1, 'title' => '學習用戶授權', 'body' => '兩種方式,Gates、策略', ) );
開始遷移
php artisan migration
我們主要使用user表(laravel自帶遷移文件)和posts表,其它創建Model等工作自行完成
- 註冊Gates,在服務提供器中註冊
服務提供器 App\Providers\AuthServiceProvider.php
/** * 註冊任意用戶認證、用戶授權服務。 * * @return void */ public function boot() { $this->registerPolicies(); Gate::define('update-post', function ($user, $post) { return $user->id == $post->user_id; }); }
- 授權判定,可以在控制器中進行權限的判定
控制器 app/Http/Controllers/PostManager.php
public function show($id)
{
$post = Post::findOrFail($id);
if (Gate::allows('update-post', $post)) {
// 指定用戶可以更新博客...
}
abort(403, 'Sorry, Permission denied');
}
以上就是一個簡單的用戶授權,是不是很簡單,下面我們理解下更多用法
1.2 編寫Gates
典型的做法是在 App\Providers\AuthServiceProvider 類中使用 Gate facade 定義。Gates 接受一個用戶實例作為第一個參數,並且可以接受可選參數。
匿名函數
public function boot() { $this->registerPolicies(); Gate::define('update-post', function ($user, $post) { return $user->id == $post->user_id; }); }
還可以使用Class@method風格字符串,比如控制器
Gate::define('update-post', 'PostPolicy@update');
使用資源Gates
一次性定義多個Gates
Gate::resource('posts', 'PostPolicy');
等同於定義了下面4個功能
Gate::define('posts.view', 'PostPolicy@view');
Gate::define('posts.create', 'PostPolicy@create');
Gate::define('posts.update', 'PostPolicy@update');
Gate::define('posts.delete', 'PostPolicy@delete');
還可以傳遞第三個參數給resource方法,以增加功能
Gate::resource('posts', 'PostPolicy', [
'image' => 'updateImage',
'photo' => 'updatePhoto',
]);
1.3 授權動作
註意: 上面我們定義Gate的時候,閉包函數第一個參數為$user, 但是你並不需要傳遞當前認證通過的用戶給這些方法。Laravel 會自動處理好傳入的用戶,然後傳遞給 gate 閉包函數
- 基本用法,使用Gate Facades
```
//允許授權
if (Gate::allows(‘update-post‘, $post)) {
// 指定用戶可以更新博客...
}
//否定授權
if (Gate::denies(‘update-post‘, $post)) {
// 指定用戶不能更新博客...
}
2. 不需自動處理用戶,自己指定一個用戶,可以使用Gate Facade的forUser()方法
if (Gate::forUser($user)->allows(‘update-post‘, $post)) {
// 指定用戶可以更新博客...
}
if (Gate::forUser($user)->denies(‘update-post‘, $post)) {
// 指定用戶不能更新博客...
}
# 2. policy策略
顯然Gates簡單易用,但是當我們需要授權的動作過多的時候,就顯得比較臃腫了,管理起來麻煩,laravel提供了我們另一種方式,來實現同樣的功能,就是policy
## 2.1 還是先看個例子
1. 創建策略文件
php artisan make:policy PostPolicy --model=Post
2. 編寫文件
以上命令會在app\Policies\PostPolicy.php文件,該文件已經包含了4個基本的「CRUD」策略方法,我們可以增刪各種方法,這裏只補充view方法
public function view(User $user, Post $post)
{
//
return $user->id == $post->user_id;
}
public function create(User $user)
{
//
}
public function update(User $user, Post $post)
{
//
}
public function delete(User $user, Post $post)
{
//
}
3. 註冊策略
服務提供器 App\Providers\AuthServiceProvider.php, 更改$policies屬性
protected $policies = [
'App\Post' => 'App\Policies\PostPolicy',
];
4. 授權判定
控制器 app/Http/Controllers/PostManager.php
public function show($id)
{
$post = Post::findOrFail($id);
if ($user->can('views', $post)) {
// 指定用戶可以 ......
}
abort(403, 'Sorry, Permission denied');
}
以上就是使用policy的一個簡單實例,下面還是進行更細致的梳理
## 2.2 編寫策略
生成策略文件後,我們可以自己創建刪除方法,以滿足我們的需求,可以為自定義策略方法使用自己喜歡的名字
public function view(User $user, Post $post)
{
//
return $user->id == $post->user_id;
}
public function create(User $user)
{
//
}
// 其它更多需要的方法
我們還可以使用策略過濾器
比如我們想要超級管理員有所有權限,可以在策略中定義一個 before 方法
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
如果你想拒絕用戶所有的授權,你應該在 before 方法中返回 false。如果返回的是 null,則通過其它的策略方法來決定授權與否。
## 2.3 授權策略
### 2.3.1 通過用戶模型
1. 指定模型的動作
Laravel 應用內置的 User 模型包含 2 個有用的方法來授權動作:can 和 cant
if ($user->can(‘update‘, $post)) {
//
}
if ($user->cant(‘update‘, $post)) {
//
}
**註意**: 如果指定模型的 策略已被註冊,can 方法會自動調用核實的策略方法並且返回 boolean 值。如果沒有策略註冊到這個模型,can 方法會嘗試調用和動作名相匹配的基於閉包的 Gate。
2. 不指定模型的動作
比較下面的兩個方法
public function view(User $user, Post $post)
{
//
return $user->id == $post->user_id;
}
public function create(User $user)
{
//
}
試想,如果我們有另個一個VideoPolice, 同樣有view和create方法。
public function view(User $user, Video $video)
{
//
return $user->id == $video->user_id;
}
public function create(User $user)
{
//
}
不難想象,盡管第一參數都是view, 第二個傳入了模型實例,我們在註冊策略的時候做了模型和策略的映射,這樣就可以區分使用的是PostPolicy還是VideoPolicy
$user->can(‘view‘, $post);
$user->can(‘view‘, $video);
那麽create方法呢, 我們可以傳遞一個類名給 can 方法。當授權動作時,這個類名將被用來判斷使用哪個策略
$user->can(‘create‘, Post::class);
$user->can(‘view‘, Video::class);
### 2.3.2 通過中間件
1. 通過隱式模型綁定,指定模型動作
use App\Post;
Route::put(‘/post/{post}‘, function (Post $post) {
// 當前用戶可以更新博客...
})->middleware(‘can:update,post‘);
關於隱式模型綁定,這裏順便提一下,更詳細的請自己查詢。
首先可以定義這樣一條路由
Route::get(‘/test/{post}‘,‘TestController@test‘);
控制器引入模型文件
use App\Http\Model\Post;
test方法傳入實例,並類型提示
public function test(Post $post)
{
http_response_code(500);
dd($post);
}
我們訪問這樣的一條路由 http://xxxx.com/test/1 ,將dd()出id=1 的post實例,我們並沒有專門的實例化post這個model,但是我們自動返回了id=參數的post實例
2. 不需要指定模型的動作
Route::post(‘/post‘, function () {
// 當前用戶可以創建博客...
})->middleware(‘can:create,App\Post‘);
### 2.3.3 通過控制器輔助函數autorize()
如果動作不被授權,authorize 方法會拋出 Illuminate\Auth\Access\AuthorizationException 異常,然後被 Laravel 默認的異常處理器轉化為帶有 403 狀態碼的 HTTP 響應:
1. 指定模型動作
public function update(Request $request, Post $post)
{
$this->authorize(‘update‘, $post);
// 當前用戶可以更新博客...
}
2. 不指定模型動作
public function create(Request $request)
{
$this->authorize(‘create‘, Post::class);
// 當前用戶可以新建博客...
}
### 2.3.4 通過blade模板
可以使用@can 和 @cannot
1. 指定模型動作
@can(‘update‘, $post)
@elsecan(‘create‘, $post)
@endcan
@cannot(‘update‘, $post)
@elsecannot(‘create‘, $post)
@endcannot
2. 不指定模型動作
@can(‘create‘, App\Post::class)
@endcan
@cannot(‘create‘, App\Post::class)
@endcannot
實際上@can 和@cannot 提供了方便的縮寫, 分別等同於下面寫法
@if (Auth::user()->can(‘update‘, $post))
@endif
@unless (Auth::user()->can(‘update‘, $post))
@endunless
```
laravel5.5授權系統