白話 Row Level Security:把「誰能看哪筆資料」交給資料庫自己管

不需要資料庫背景也能懂的 RLS 入門。用一個「共用檔案櫃」的比喻,說明為什麼把權限規則寫在資料庫層,比寫在程式裡更難出錯。

#postgresql#rls#資料庫#資安#科普

重點摘要

同一個系統裡有很多使用者,每個人只該看到自己的資料。這件事要在哪裡把關?多數系統寫在程式碼裡(每個功能自己檢查一次),但只要有一個地方漏檢查,就洩漏。RLS(Row Level Security)換一個做法:把規則寫進資料庫,讓資料庫在每次查詢時自動把關。這篇用一個比喻講清楚它是什麼、為什麼更安全,以及它的代價。


一個生活比喻:共用的檔案櫃

想像一間診所有一個大檔案櫃,醫師、護理師、藥師、櫃檯都要用它。每個人只該打開自己權限內的抽屜。

有兩種管法:

  • 做法 A:門口放一位櫃檯人員。每次有人來拿資料,櫃檯先問「你是誰、你能不能看這個」,確認後才給。問題是——櫃檯會累、會忙中出錯、換人代班時可能忘了問。只要有一次沒問,資料就給錯人了。
  • 做法 B:檔案櫃本身認鑰匙。每個抽屜的鎖只認得特定的鑰匙,你的鑰匙打不開別人的抽屜。就算沒有櫃檯,櫃子自己就擋住了。

應用層權限檢查就是做法 A:權限判斷寫在程式裡,每個功能都要自己記得檢查。RLS 是做法 B:權限規則長在資料庫上,任何人來查,資料庫都自動只回傳他有權看的資料列。


傳統做法為什麼容易漏

在程式裡檢查權限,大概長這樣(用白話寫):

「有人要查病人清單 → 先確認他是醫師 → 而且只給他自己診間的病人。」

問題出在「每個功能都要自己記得做這件事」。一個系統可能有好幾十個功能入口,只要其中一個:

  • 工程師趕時間忘了加那句「只給自己診間的」,
  • 或是兩段查詢接起來時,第一段檢查了、第二段忘了,

資料就外流了。這不是假設,是很常見的真實漏洞。人為疏漏會隨功能變多而累積。


RLS 是什麼

PostgreSQL 這類資料庫提供一個功能:你可以幫每一張資料表訂一條規則(policy),描述「哪一筆資料,對哪個登入者可見」。訂好之後,每一次查詢資料庫都會自動套用這條規則,不管這個查詢是從哪個功能、哪個程式發出來的。

用白話寫一條規則的意思:

「病人資料表:只有當登入者是醫師、而且這位病人屬於他的診間時,這一列才對他可見。」

訂好這條之後,程式碼那邊就不用再自己寫「先確認他是醫師…」了——資料庫會自己擋。工程師就算忘了檢查,資料庫也不會把不該給的資料吐出來。


為什麼這樣更安全

把規則收進資料庫層,有幾個直接的好處:

  1. 單一事實來源:規則只寫一次、寫在資料庫。以後多了手機 App、後台工具、第三方串接,全部共用同一套規則,不用每個各寫一遍。
  2. 繞不過去:就算攻擊者想辦法跳過你的程式、直接對資料庫下指令,規則還是會套用。防線在最底層。
  3. 關聯查詢也自動處理:查「這位病人的處方」需要同時碰兩張表時,資料庫對兩張表都會套規則,不會因為接起來就漏掉。

一句話:做法 A 靠「每個人都記得問」,做法 B 靠「櫃子本身就鎖住」。 後者少了「人會忘」這個最大的破口。


誠實說:它不是免費的

RLS 不是萬靈丹,代價很實在:

  • 除錯比較難:當規則擋住一筆資料時,資料庫回你的是「沒有這筆」,你分不清是「真的沒有」還是「被規則擋住」。要另外用管理權限重跑一次才知道。
  • 複雜查詢會變慢:規則要對每一筆資料檢查,資料量大又牽涉多張表時,效能會下降,需要額外調校。
  • 學習曲線陡:這套思路跟「在程式裡寫 if 判斷」不一樣,第一次上手要花時間。改資料表結構時,也要記得同步改規則。

所以它適合多角色、資料敏感的系統(醫療、金融、多租戶服務);如果只是單人、單一角色的小工具,用它反而是殺雞用牛刀。


我為什麼選它

我在診所端系統 ExClinCalc 裡,用 14 張表、29 條 RLS 規則,管六種角色(醫師、護理師、藥師、行政、管理員、超級管理員)能看到什麼。對醫療資料來說,「應用層被攻破,資料庫這關還在」這件事值得我付上面那些代價。

如果你想看更深入的取捨(為什麼不只用應用層、五種常見失敗模式、實際的規則設計原則),我在另一篇寫得更完整。


延伸閱讀