使用者只能看到自己的編輯按鈕,卻把網址裡的文章編號換掉,就能修改別人的文章。問題不在按鈕藏得不夠好,而是伺服器收到更新請求後,沒有確認這筆資料是否允許由目前使用者修改。
Laravel 的 Policy 可以把「誰能對哪一筆資料做什麼」放到同一個地方。這篇以作者只能修改本人文章為例,先建立規則,再在寫入前執行授權。登入、表單格式與資源所有權各自需要核對,不能只通過其中一項。
先找到真正要更新的那一筆資料
更新入口通常會帶文章 ID。後端要先取得對應的文章,再對這個模型做授權;不要只核對列表頁曾經顯示過哪些 ID。使用者可以改網址,也可以直接發送請求,前端頁面不是伺服器授權的依據。
假設文章的 user_id 表示作者,伺服器目前登入者的 ID 表示操作者,就比較這兩個值。作者欄位應取自資料庫中已保存的文章,不是表單傳入的隱藏欄位。否則使用者把隱藏欄位也改成自己,就可能讓錯誤檢查通過。
多租戶或團隊共用資料時,規則可能不只比作者 ID,還要核對所屬組織、角色或委派權限。先寫清楚你的產品允許誰做什麼,再決定 Policy 的條件。下面只處理最小的「本人文章」規則,不替管理員或團隊設計所有權限。
在 Policy 寫出更新規則
可用 Artisan 產生與文章模型對應的 Policy:
php artisan make:policy PostPolicy --model=Post
在 update 方法接收操作者與文章,判斷作者是否一致。模型若使用整數 ID,先確認兩邊的型別設定一致;若用 UUID,就按實際欄位型別處理,不要為了讓比較通過而把所有識別碼隨意轉成整數。
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
Laravel 可以依符合慣例的模型與 Policy 位置自動尋找對應。若專案目錄或命名不符合慣例,明確註冊對應,並確認應用程式啟動時有執行。只建立 Policy 檔案卻從未呼叫,更新流程不會因此自動受到保護。
Gate::policy(Post::class, PostPolicy::class);
授權必須發生在寫入之前
控制器取得文章後,先執行 Gate::authorize('update', $post),再驗證可修改欄位並更新。拒絕時會拋出授權例外;在正常 Laravel HTTP 處理流程中,預設會轉成 403 回應。不能先保存文章,再用授權結果決定要不要顯示成功提示。
public function update(Request $request, Post $post)
{
Gate::authorize('update', $post);
$validated = $request->validate([
'title' => ['required', 'string', 'max:160'],
]);
$post->update($validated);
return redirect()->back();
}
這是已經有登入保護與路由模型綁定的應用程式片段,Gate 及模型類別仍需正確匯入。若你使用 Form Request 的 authorize 方法或路由 can 中介層,也可在那裡安排授權;選好入口後,確認所有更新途徑都經過它。
不要把表單送來的所有欄位直接交給模型。作者 ID、租戶 ID 或角色通常不是這個編輯表單允許修改的內容。明確驗證並保存可編輯欄位,授權時也仍以資料庫中的資源歸屬為準。
用兩個使用者核對同一筆文章
我們另建合成 SQLite 資料,建立使用者甲、乙和一篇屬於甲的文章。透過 Laravel 13 的實際 Gate API 註冊 Policy,再用 forUser 分別指定操作者,執行授權後才呼叫 Eloquent 更新。
甲的授權通過,文章從「原始標題」改成「本人修改」。乙對同一篇文章執行更新時,框架拋出 AuthorizationException,後面的保存沒有執行;重新讀取資料,標題仍是「本人修改」。
甲:授權通過 → 保存「本人修改」
乙:授權拒絕 → 未保存「他人企圖修改」
重新讀取:仍為「本人修改」
這個案例核的是 Policy 與資料寫入順序,沒有建立完整的登入或瀏覽器表單。套到自己的網站後,還要使用兩個合成測試帳號,從實際更新路由發送請求,核對回應與資料。不要拿真客戶帳號試著改別人的內容。
隱藏按鈕仍有用途,但不能代替後端
畫面可依同一授權規則顯示編輯入口,讓沒有權限的人不必先點進去才被拒絕。這是介面上的安排;即使按鈕不存在,伺服器仍要拒絕直接到達的未授權請求。
也要分清 CSRF 和所有權。CSRF 保護請求來源情境,登入確認目前是誰,格式驗證確認標題等輸入是否合法;它們都不直接回答這個人能否更新這一篇文章。保留原有保護,再加上針對資源的授權。
若網站使用不同登入守衛或 API 驗證方式,確認授權拿到的是正確使用者。不要把請求中的 user_id 當成登入身分,也不要為了讓示例能跑而跳過實際驗證中介層。
找出還有哪些更新入口
同一筆資料可能有網頁表單、API、批次操作和管理工具。先列出入口,逐一看它們是否在寫入前使用一致的規則。批次更新尤其不能只查其中一筆就放行全部;每筆資源或整個批次範圍,都要按產品規則授權。
回歸時至少包含本人、他人、未登入及不存在的文章。不存在的資料與授權拒絕是不同情況;若產品需避免揭露資源是否存在,可依官方授權回應方法另定回應策略。不要在公開錯誤文字中顯示別人的文章內容或私人作者資料。
拒絕請求後,再直接查那筆文章,確認資料沒有改動。只有頁面顯示「沒有權限」還不夠,因為程式可能先保存才顯示提示。找到缺口後,修正更新入口和相關測試,讓所有權檢查成為真正的寫入前條件。

評論0