除了提供內建的 authentication(第 11 天:用戶認證)服務外,Laravel 還提供了一種可以簡單就進行使用的方法,來管理使用者與資源的授權
關係。
Laravel 提供了兩種主要的授權方法:攔截器(Gates)
和策略(Policies)
。
這兩者幫助開發者管理使用者與應用程式中資源的權限關係,即使使用者已通過身份驗證,也可能沒有權限修改或刪除重要資料。
文件:用户授权
角色與權限
定義使用者角色(如管理員、編輯、一般使用者)和權限(如檢視、編輯、刪除),可以使用 Spatie/laravel-permission
等套件來管理複雜的角色和權限系統。
Gate 和 Policies
Laravel 提供了 Gate 和 Policies 來處理簡單和複雜的權限檢查。
攔截器(Gate)
是一種輕量級的授權方法,類似於路由,它適用於沒有直接與模型或資源關聯的權限檢查。例如,可以使用攔截器來控制是否允許使用者存取管理員儀錶板。它透過閉包函數定義授權邏輯,適合簡單的權限驗證場景。策略(Policies)
則較適合複雜的授權需求。策略像控制器一樣,將與特定模型或資源相關的授權邏輯集中管理。每個策略類別包含多個方法,每個方法對應一個授權操作。策略非常適合處理特定模型的授權規則,例如判斷使用者是否有權編輯某個文章或刪除某則評論。// 定義 Gate
Gate::define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
文件:中间件
中間件
用於在請求到達控制器之前或之後對請求進行處理,用於驗證使用者是否有存取特定資源的權限。
授權中間件
可以透過定義中間件來檢查使用者是否有某種權限。
// web.php 路由文件
Route::group(['middleware' => ['can:update-post']], function () {
Route::post('posts/{post}/update', [PostController::class, 'update']);
});
策略
定義一個策略類別來集中處理特定模型的授權邏輯。
策略方法
策略類別包含多個方法,每個方法對應於一個特定的授權操作。
建立流程
step 1 - 創建策略
下指令 php artisan make:policy PostPolicy
,並在 app/Policies/PostPolicy.php
定義方法
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
class PostPolicy
{
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
step 2 - 註冊策略
// AuthServiceProvider.php
use App\Models\Post;
use App\Policies\PostPolicy;
protected $policies = [
Post::class => PostPolicy::class,
];
step 3 - 使用策略
在控制器中的方法中執行更新操作
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
}
延續第 9 天:表單處理與請求,把每個角色的權限加上,例如:使用者可以發布、編輯和刪除文章,但只有文章作者或管理員才能執行這些操作。
因為前面沒有建立資料庫,所以今天的練習就從資料庫開始規畫建立!
step 1 - 表格規劃
有使用者就需要使用者 id,一篇文章會需要文章 id、標題、內容,這樣就會出現 2 個模型,前面的第 5 天:數據模型與遷移有提到數據表和模型之間的關係,他們倆個之間的關係比較像一個作者可以寫好多篇文章,但是一篇文章只能出自一人之手,所以算是一對多
的關係。
step 2 - 建立模型和遷移文件
指令 php artisan make:model User -m
和 php artisan make:model Post -m
模型
在 User
定義對 Post
一對多(hasMany
)的關係;在 Post
增加對 User
的反向(belongsTo
)關係
// app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected $fillable = ['name'];
public function posts()
{
return $this->hasMany(Post::class);
}
}
// app/Models/Post.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $fillable = ['title', 'content', 'user_id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
遷移文件
在規劃中知道外鍵要建立在 'posts' 表格中
// database/migrations/2024_08_26_create_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePostsTable extends Migration
{
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('posts');
}
}
// database/migrations/2024_08_26_create_users_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('users');
}
}
step 3 - 建立資料庫
指令 php artisan migrate
建立資料庫
step 4 - 創建策略
指令 php artisan make:policy PostPolicy
建立策略類,並且在裡面寫授權邏輯。
// app/Policies/PostPolicy.php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
public function update(User $user, Post $post)
{
return $user->id === $post->user_id || $user->is_admin;
}
public function delete(User $user, Post $post)
{
return $user->id === $post->user_id || $user->is_admin;
}
}
step 5 - 註冊策略
在 app/Providers/AuthServiceProvider.php
中註冊策略
namespace App\Providers;
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
Post::class => PostPolicy::class,
];
public function boot()
{
$this->registerPolicies();
}
}
step 6 - 建立控制器 & 處理表單驗證
指令 php artisan make:controller PostController
,並且在控制器 app/Http/Controllers/PostController.php
中新增方法來處理文章的 CRUD 操作。
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Gate;
class PostController extends Controller
{
public function edit(Post $post)
{
$this->authorize('update', $post);
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post): RedirectResponse
{
$this->authorize('update', $post);
$post->update($request->all());
return redirect()->route('posts.index')->with('success', 'Post updated successfully!');
}
public function destroy(Post $post): RedirectResponse
{
$this->authorize('delete', $post);
$post->delete();
return redirect()->route('posts.index')->with('success', 'Post deleted successfully!');
}
}
step 7 - 設定路由
在 routes/web.php
中設定路由
use App\Http\Controllers\PostController;
Route::resource('posts', PostController::class)->middleware('auth');
step 8 - 創建視圖
建立前端的視圖 resources/views/posts/edit.blade.php
<!DOCTYPE html>
<html>
<head>
<title>Edit Post</title>
</head>
<body>
<h1>Edit Post</h1>
<form action="{{ route('posts.update', $post) }}" method="POST">
@csrf
@method('PUT')
<label for="title">Title:</label>
<input type="text" id="title" name="title" value="{{ $post->title }}">
<label for="content">Content:</label>
<textarea id="content" name="content">{{ $post->content }}</textarea>
<button type="submit">Update Post</button>
</form>
</body>
</html>
step 9 - 測試
$this->authorize()
方法來檢查使用者是否有權限執行某些操作(如編輯或刪除)。