重點摘要
同一個系統裡有很多使用者,每個人只該看到自己的資料。這件事要在哪裡把關?多數系統寫在程式碼裡(每個功能自己檢查一次),但只要有一個地方漏檢查,就洩漏。RLS(Row Level Security)換一個做法:把規則寫進資料庫,讓資料庫在每次查詢時自動把關。這篇用一個比喻講清楚它是什麼、為什麼更安全,以及它的代價。
一個生活比喻:共用的檔案櫃
想像一間診所有一個大檔案櫃,醫師、護理師、藥師、櫃檯都要用它。每個人只該打開自己權限內的抽屜。
有兩種管法:
- 做法 A:門口放一位櫃檯人員。每次有人來拿資料,櫃檯先問「你是誰、你能不能看這個」,確認後才給。問題是——櫃檯會累、會忙中出錯、換人代班時可能忘了問。只要有一次沒問,資料就給錯人了。
- 做法 B:檔案櫃本身認鑰匙。每個抽屜的鎖只認得特定的鑰匙,你的鑰匙打不開別人的抽屜。就算沒有櫃檯,櫃子自己就擋住了。
應用層權限檢查就是做法 A:權限判斷寫在程式裡,每個功能都要自己記得檢查。RLS 是做法 B:權限規則長在資料庫上,任何人來查,資料庫都自動只回傳他有權看的資料列。
傳統做法為什麼容易漏
在程式裡檢查權限,大概長這樣(用白話寫):
「有人要查病人清單 → 先確認他是醫師 → 而且只給他自己診間的病人。」
問題出在「每個功能都要自己記得做這件事」。一個系統可能有好幾十個功能入口,只要其中一個:
- 工程師趕時間忘了加那句「只給自己診間的」,
- 或是兩段查詢接起來時,第一段檢查了、第二段忘了,
資料就外流了。這不是假設,是很常見的真實漏洞。人為疏漏會隨功能變多而累積。
RLS 是什麼
PostgreSQL 這類資料庫提供一個功能:你可以幫每一張資料表訂一條規則(policy),描述「哪一筆資料,對哪個登入者可見」。訂好之後,每一次查詢資料庫都會自動套用這條規則,不管這個查詢是從哪個功能、哪個程式發出來的。
用白話寫一條規則的意思:
「病人資料表:只有當登入者是醫師、而且這位病人屬於他的診間時,這一列才對他可見。」
訂好這條之後,程式碼那邊就不用再自己寫「先確認他是醫師…」了——資料庫會自己擋。工程師就算忘了檢查,資料庫也不會把不該給的資料吐出來。
為什麼這樣更安全
把規則收進資料庫層,有幾個直接的好處:
- 單一事實來源:規則只寫一次、寫在資料庫。以後多了手機 App、後台工具、第三方串接,全部共用同一套規則,不用每個各寫一遍。
- 繞不過去:就算攻擊者想辦法跳過你的程式、直接對資料庫下指令,規則還是會套用。防線在最底層。
- 關聯查詢也自動處理:查「這位病人的處方」需要同時碰兩張表時,資料庫對兩張表都會套規則,不會因為接起來就漏掉。
一句話:做法 A 靠「每個人都記得問」,做法 B 靠「櫃子本身就鎖住」。 後者少了「人會忘」這個最大的破口。
誠實說:它不是免費的
RLS 不是萬靈丹,代價很實在:
- 除錯比較難:當規則擋住一筆資料時,資料庫回你的是「沒有這筆」,你分不清是「真的沒有」還是「被規則擋住」。要另外用管理權限重跑一次才知道。
- 複雜查詢會變慢:規則要對每一筆資料檢查,資料量大又牽涉多張表時,效能會下降,需要額外調校。
- 學習曲線陡:這套思路跟「在程式裡寫 if 判斷」不一樣,第一次上手要花時間。改資料表結構時,也要記得同步改規則。
所以它適合多角色、資料敏感的系統(醫療、金融、多租戶服務);如果只是單人、單一角色的小工具,用它反而是殺雞用牛刀。
我為什麼選它
我在診所端系統 ExClinCalc 裡,用 14 張表、29 條 RLS 規則,管六種角色(醫師、護理師、藥師、行政、管理員、超級管理員)能看到什麼。對醫療資料來說,「應用層被攻破,資料庫這關還在」這件事值得我付上面那些代價。
如果你想看更深入的取捨(為什麼不只用應用層、五種常見失敗模式、實際的規則設計原則),我在另一篇寫得更完整。
延伸閱讀
- 為什麼我選擇 PostgreSQL Row Level Security 而不是應用層權限檢查 ── 同主題的進階版,含實際程式碼與五種失敗模式
- ExClinCalc Case Study ── 看 29 條規則在真實系統裡怎麼運作