# Lesson 10: 守門員的權限：授權 - RBAC 與 Policy 設計

在上一堂課，我們學會了如何驗證「你是誰」（Authentication）。但確認身分只是第一步。

想像你是一間銀行的員工。你刷卡通過大門（Authentication 成功），但这不代表你可以直接走進金庫搬錢。**「你能做什麼？」** 這就是 **授權 (Authorization)** 的範疇。

對於後端工程師來說，如果說 Authentication 是守大門的警衛，那麼 Authorization 就是每一扇門上的電子鎖與守門員，它決定了請求是否會被拒絕（403 Forbidden）。

# 核心觀念：Authentication vs. Authorization

| **特性** | **Authentication (驗證)** | **Authorization (授權)** |
| --- | --- | --- |
| **關鍵問題** | 你是誰？ (Who are you?) | 你可以做這件事嗎？ (Can you do this?) |
| **失敗狀態碼** | **401 Unauthorized** (通常指未登入) | **403 Forbidden** (已登入但權限不足) |
| **發生時機** | 請求最前端，確認身分 | 確認身分後，執行具體邏輯前 |
| **生活案例** | 出示護照通關 | 持有頭等艙機票進入貴賓室 |

# RBAC：角色基礎存取控制 (Role-Based Access Control)

在小型專案中，你可能會寫出這樣的程式碼：

```php
if ($user->id === 1) {
    // 允許刪除文章
}
```

這種寫法難以維護。當系統變大，我們需要更結構化的管理方式。這就是 **RBAC**。

## 什麼是 RBAC？

RBAC 的核心概念是：**權限不直接綁定在「人」身上，而是綁定在「角色」身上。**

1. **User (使用者)**：系統的操作者。
    
2. **Role (角色)**：一組權限的集合（例如：管理員、編輯、一般會員）。
    
3. **Permission (權限)**：具體可以執行的動作（例如：刪除文章、編輯商品）。
    

## 資料庫設計範例

一個標準的 RBAC 通常涉及五張表（或簡化為三張）：

1. `users` (使用者)
    
2. `roles` (角色)
    
3. `permissions` (權限) - *選用，簡單系統可直接定義在 Role 上*
    
4. `role_user` (使用者與角色的關聯 - 多對多)
    
5. `permission_role` (角色與權限的關聯 - 多對多)
    

透過這種設計，當有新進員工時，你不需要逐一開啟 50 個功能的權限，只需將他指派為「編輯 (Editor)」角色即可。

## **RBAC 補充：權限粒度與層次**

RBAC 核心概念是 **權限不綁定個人，而是綁定角色**。在實務中，RBAC 的設計還可以進一步補充以下概念：

### 1\. Permission 粒度

* **粗粒度角色**：Admin、Editor、User
    
    適合小型系統或權限需求單純的情境。
    
* **細粒度權限**：例如「刪除文章」「編輯文章」「審核留言」
    
    好處是更靈活，角色可以組合多個細節權限，不必新增角色就能控制更多行為。
    

### 2\. 層次化角色 (Hierarchical RBAC)

* 大型系統中，角色可以繼承權限。
    
    例如：
    
    ```plaintext
    User < Editor < Admin
    ```
    
* Editor 自動擁有 User 權限，Admin 擁有 Editor + User 權限
    
* 好處：減少重複設定，維護方便
    

### 3\. 動態角色與條件權限

* 有些權限需要依情境動態授予，例如：
    
    * 專案負責人可以編輯專案
        
    * 訂單管理員可以修改自己部門的訂單
        
* 這通常會搭配 Policy 或 Gate 做進一步檢查
    

> 總結：RBAC 不只是 User → Role → Permission，更是一個 設計思維，可以控制粒度、層次與動態條件。

# Policy 設計：將業務邏輯與控制器分離

在現代後端框架（如 Laravel, Django, NestJS）中，我們通常不會在 Controller/Service 裡面寫又臭又長的 `if-else` 來檢查權限。我們使用 **Policy (策略模式)**。

Policy 的核心目的，就是將「能不能做 (Can)」的邏輯，從「實際去做 (Do)」的邏輯中拆分出來。

這裡不看code，真的很難繼續講下去，我們來看個code 經典範例吧。

以下範例code由Laravel 12 實現。

## 情境：部落格文章的修改權限

假設規則如下：

1. **管理員 (Admin)** 可以修改任何人的文章。
    
2. **作者 (Author)** 只能修改「自己」寫的文章。
    
3. **一般人** 不能修改文章。
    

### Bad Smell Code

我們當然可以這樣寫

```php
//Service
$post = $this->postRepository->find($postId);

if($user->role == User::ADMIN || $post->created_by == auth()->user()->id){
	$this->postRepostory->update($updateData , $post->id);
}else {
  abort(403);
}

//Repository
function update($data, $postId)
{
	return $this->model::where('id', $postId)->update($data);
}
```

但有些bad smell 對吧？

很明顯有：可讀性與維護性缺失，業務邏輯 和 授權邏輯 混在一起，程式碼會變得越來越長。

### Modify Step

1. 新增Policy檔案
    

```bash
php artisan make:policy PostPolicy --model=Post
#--model=Post 可選
```

1. (可選)如果Model File Name沒有符合Laravel auto load
    

我這裡故意用Postssss當Model Name來說明

```php
//AppServiceProvider
Gate::policy(Postssss::class, Postolicy::class);

//或者
#[UsePolicy(PostPolicy::class)]
class Postssss extends Model
{
}
```

1. Policy Update
    

```php
public function update(User $user, Post $post)
{
   return $user->role === User::ADMIN
        || $post->created_by === $user->id;
}
```

或者

```php
class PostPolicy
{
    /**
     * 在所有其他檢查之前執行
     * 如果回傳 true，則直接允許；回傳 null 則繼續往下檢查
     */
    public function before(User $user, $ability)
    {
        // 規則 1: Admin 可以修改任何人的文章
        if ($user->role === User::ADMIN) {
            return true;
        }
    }

    /**
     * 判斷 User 是否可以更新 Post
     */
    public function update(User $user, Post $post)
    {
        // 規則 2: 作者只能修改「自己」的文章
        // 規則 3: 一般人不能修改 (因為不是 Admin 也不是作者，這裡會回傳 false)
        return $user->id === $post->created_by;
    }
}
```

1. 修改Service
    

```php
$post = $this->postRepository->find($postId);

Gate::authorize('update', $post);

$this->postRepository->update($updateData, $postId);
```

或

這個會回傳 在 Policy中的return

```php
auth()->user()->can('update' , $post);
```

或

這個會回傳 Auth/Access/Response

```php
$response = Gate::inspect('update' , $post)
$response->allowed()
```

### 業務邏輯與權限邏輯分離比較

| 項目 | Service 判斷 | 使用 Policy |
| --- | --- | --- |
| 權限判斷位置 | Service | Policy（官方建議） |
| 可維護性 | 中等 | 高（集中管理） |
| 擴展角色邏輯 | 需要修改 Service | 修改單一 Policy |
| 可讀性 | 可以，但會累積 | 清楚：Service=流程、Policy=權 |

# 常見陷阱

**隱藏不等於後端安全**： 不要以為在前端把「刪除按鈕」藏起來，駭客就刪不掉資料。後端 API **每一支** 修改資料的接口都必須有 Authorization 檢查。

1. **IDOR (Insecure Direct Object References)**： 這是最常見的漏洞。例如 API 是 `GET /orders/1234`，如果後端只檢查「使用者是否登入」，而沒檢查「訂單 1234 是否屬於該使用者」，那麼惡意使用者可以遍歷 ID 偷看別人的訂單。Policy 就是防止 IDOR 的最佳防線。
    
2. **超級管理員 (Super Admin)**： 設計 Policy 時，可以設計一個 `before` 過濾器，讓 Super Admin 繞過所有檢查，避免你在開發時把自己鎖在外面。
