iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0
佛心分享-IT 人自學之術

後端小白自學 Laravel系列 第 12

第 12 天:用戶授權

  • 分享至 

  • xImage
  •  

除了提供內建的 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;
});

使用中間件進行授權(預計第 15 天分享中間件)


文件:中间件

中間件
用於在請求到達控制器之前或之後對請求進行處理,用於驗證使用者是否有存取特定資源的權限。

授權中間件
可以透過定義中間件來檢查使用者是否有某種權限。

// web.php 路由文件
Route::group(['middleware' => ['can:update-post']], function () {
    Route::post('posts/{post}/update', [PostController::class, 'update']);
});

使用策略(Policies)進行複雜授權


策略
定義一個策略類別來集中處理特定模型的授權邏輯。

策略方法
策略類別包含多個方法,每個方法對應於一個特定的授權操作。

建立流程

  • 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 -mphp artisan make:model Post -m

  • 模型
    User 定義對 Post 一對多(hasMany)的關係;在 Post 增加對 User 的反向(belongsTo)關係

    文件:Eloquent: 关联 - 一对多

    // 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 - 建立控制器 & 處理表單驗證

文件:Eloquent: 入门 - 插入和更新模型

指令 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() 方法來檢查使用者是否有權限執行某些操作(如編輯或刪除)。
  • 策略:策略類別集中處理複雜的授權邏輯,確保只有授權使用者能夠執行特定操作。

上一篇
第 11 天:用戶認證
下一篇
第 13 天:前端 Vue3 搭配後端 Laravel 9.x
系列文
後端小白自學 Laravel30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言