Logo

新人日誌

首頁關於我部落格

新人日誌

Logo

網站會不定期發佈技術筆記、職場心得相關的內容,歡迎關注本站!

網站
首頁關於我部落格
部落格
分類系列文

© 新人日誌. All rights reserved. 2020-present.

資料庫權限不只有 GRANT——從 Database 到 Cell,五層安全性完整解析(含 RLS 實作)

最後更新:2026年7月16日資料庫

當一個資料庫只有你一個人用的時候,權限根本不是問題——反正所有資料都是你的。

但當團隊開始共用同一個資料庫,問題就來了。

HR 不應該看到客戶訂單的資料表,業務部門不應該改到員工個資的資料表,而同一張員工資料表裡,經理能看到薪水欄位,但一般員工不行。

這時候你就需要「權限控管」——決定誰能看什麼、誰能改什麼。

但資料庫的權限控管其實比大多數人想的複雜得多。

它不是一個開關,而是五個層級,從「你能不能連進這個資料庫」一直細到「這個格子的值要不要讓你看到」。

很多人只設了最外面的一兩層就覺得搞定了,結果裡面三層全是漏洞。

這篇文章會從最外層到最內層,把五個安全性層級完整拆解。

看完之後,你會知道每一層各負責什麼、解決什麼問題,以及它們該怎麼搭配使用。

資料庫安全性的五個層級——先看全貌

在開始逐層拆解之前,先用一張全景圖來理解整體架構。

你可以把資料庫的安全性想像成一顆洋蔥,從外到內共有五層:

  1. Database Level(資料庫層級)——你能不能連進這個資料庫?
  2. Object / Table Level(物件 / 資料表層級)——你能操作哪些資料表?
  3. Column-Level Security(CLS)——同一張表裡,你能看到哪些欄位?
  4. Row-Level Security(RLS)——同一張表裡,你能看到哪些資料列?
  5. Cell-Level Security——精確到某個「格子」,要不要讓你看到裡面的值?

每一層控管的粒度不同,解決的問題也不同。

接下來會從最外層開始,一層一層往內拆。

Database Level——你連門都進不去

這是最外面的一層,決定的是:這個使用者能不能連進這個資料庫?

你可以把它想像成大樓的門禁卡。

沒有門禁卡,你連大廳都進不去,更別說裡面的辦公室了。

在 SQL Server 裡,控制權限的兩個基本指令是 GRANT(授權)和 DENY(拒絕)。

GRANT 就是「給你這個權限」,DENY 就是「明確禁止你做這件事」。

這一層主要涉及幾種權限:

  • CONNECT:允許使用者連線到資料庫
  • CREATE TABLE、CREATE VIEW:允許使用者在資料庫裡建立物件
  • ALTER:允許使用者修改資料庫的結構

舉個例子,如果你想讓某個使用者只能連進資料庫、但不能建立任何東西,可以這樣寫:

GRANT CONNECT TO [SomeUser];
DENY CREATE TABLE TO [SomeUser];

這一層的重點是:先決定誰可以進來。

但你可能會想:「我平常用資料庫,從來沒打過 GRANT 或 DENY,不是一樣能正常連線嗎?」

這是因為你用的帳號很可能本身就擁有最高權限。

例如 SQL Server 的 sa 帳號,或是你的帳號被加進了 sysadmin、db_owner 這些角色裡。

這些高權限角色預設就擁有所有權限,所以你不需要額外 GRANT 任何東西,自然感覺不到這層的存在。

但在正式的生產環境裡,不可能每個人都用 sa 帳號——這就像把大樓的萬能鑰匙發給每個人一樣危險。

所以才需要透過 GRANT 和 DENY 來精確控制每個使用者的權限。

Object / Table Level——進了門,但哪些房間能進?

通過了 Database Level 的門禁,使用者已經進到了資料庫裡面。

但這不代表他可以碰所有的資料表。

這一層控管的是:使用者對每一張資料表(或 View、Stored Procedure 等物件)有什麼操作權限?

延續大樓的比喻:你有門禁卡進了大樓,但每間辦公室的門鎖不同,你不見得每間都能進。

在 SQL Server 裡,常見的操作是 GRANT 和 DENY:

-- 允許 SomeUser 查詢 Employees 資料表
GRANT SELECT ON dbo.Employees TO [SomeUser];

-- 禁止 SomeUser 刪除 Employees 的資料
DENY DELETE ON dbo.Employees TO [SomeUser];

這裡的 dbo 是 SQL Server 預設的 Schema(結構描述),你可以把它想成資料表的「資料夾」。

dbo.Employees 就是「dbo 這個資料夾底下的 Employees 資料表」。

大多數情況下,資料表都放在 dbo 底下,所以你會一直看到這個前綴。

你可以針對每張資料表分別設定 SELECT、INSERT、UPDATE、DELETE 的權限。

如果沒有給 SELECT 權限,使用者連 SELECT * FROM Employees 都會被擋下來,直接報錯。

這一層的重點是:控制使用者能碰哪些資料表,以及能做哪些操作。

很多專案只做到這一層就停了,覺得「我已經限制他只能查某幾張表了」。

但問題是——他雖然只能查這張表,卻可能看到表裡所有的欄位和所有的資料列。

這就是接下來三層要解決的事。

Column-Level Security(CLS)——同一張表,你只能看特定欄位

假設你有一張 Employees 資料表,裡面有這些欄位:

EmployeeIDNameDepartmentSalarySSN
1AliceEngineering95000A123…
2BobMarketing78000B456…
NameAlice
DepartmentEngineering
Salary95000
SSNA123…
NameBob
DepartmentMarketing
Salary78000
SSNB456…

部門經理需要查這張表來看自己部門的人員名單。

但他不應該看到 Salary(薪水)和 SSN(身分證字號)。

這時候就需要 Column-Level Security(CLS),也就是欄位層級的權限控管。

在 SQL Server 裡,CLS 的做法很直覺——直接用 GRANT 指定哪些欄位可以查:

-- 只允許部門經理查 EmployeeID、Name、Department 三個欄位
GRANT SELECT ON dbo.Employees (EmployeeID, Name, Department) TO [DeptManager];

設定完之後,如果 DeptManager 執行 SELECT * FROM Employees,資料庫會直接報錯,因為他沒有 Salary 和 SSN 的 SELECT 權限。

他必須明確指定自己有權限的欄位:

SELECT EmployeeID, Name, Department FROM dbo.Employees;

你可能會想:「用 View 不是也可以做到一樣的事嗎?」

沒錯,建一個只包含特定欄位的 View 確實也能達到類似效果。

但 CLS 和 View 的差別在於:

  • CLS 是在資料表層級直接控管權限,不需要額外建立物件,管理上比較集中。
  • View 是建立一個虛擬的「窗口」,透過它來限制可見的欄位。但如果使用者有原始資料表的 SELECT 權限,他可以繞過 View 直接查原表。

所以在需要嚴格控管的場景下,CLS 比 View 更可靠。

這一層的重點是:就算使用者能查這張表,也不代表他能看到所有欄位。

CLS 管「欄」,接下來的 RLS 管「列」,它們是最佳搭擋。

Row-Level Security(RLS)——同一張表,你只能看特定資料列

前面的 CLS 管的是「哪些欄位能看」,而 RLS 管的是「哪些資料列能看」。

RLS 解決的問題是:同一張資料表裡,不同的使用者只能看到屬於自己的資料列。

RLS 的核心概念:Security Policy + Predicate Function

RLS 的運作原理其實不複雜,核心只有兩個東西:

  1. Predicate Function(條件判斷函數):一個你自己寫的函數,資料庫每次查詢時會拿每一列的資料去問它:「這一列要不要讓這個使用者看到?」函數回答「要」就顯示,回答「不要」就過濾掉。這裡的 Predicate 你可以理解成「篩選條件」,就像 WHERE 後面接的那個條件一樣。
  2. Security Policy(安全性原則):光寫好函數還不夠,你需要告訴資料庫「這個函數要套用在哪張資料表上」。Security Policy 就是做這件事的——它把函數和資料表綁在一起,讓篩選自動生效。

簡單來說:Predicate Function 是「規則」,Security Policy 是「把規則貼到資料表上」的動作。

規則寫好但沒貼上去,不會生效;貼上去但沒寫規則,也沒東西可以篩。兩個缺一不可。

實作範例:多部門的員工資料表

假設 Employees 資料表長這樣:

EmployeeIDNameDepartmentSalary
1AliceEngineering95000
2BobMarketing78000
3CarolEngineering88000
NameAlice
DepartmentEngineering
Salary95000
NameBob
DepartmentMarketing
Salary78000
NameCarol
DepartmentEngineering
Salary88000

你希望 Engineering 的經理只能看到 Engineering 的資料,Marketing 的經理只能看到 Marketing 的資料。

第一步,建立 Predicate Function:

這段程式碼在做的事情很單純:

建立一個函數,讓資料庫在每次查詢時拿來判斷「這一列要不要給這個使用者看」。

判斷的邏輯是:如果這一列的部門名稱,跟目前登入的使用者名稱相同,就讓他看;不同就過濾掉。

CREATE FUNCTION dbo.fn_SecurityPredicate(@Department NVARCHAR(100))
RETURNS TABLE
WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS result
    WHERE @Department = USER_NAME();

其中幾個關鍵字的意思:

  • @Department NVARCHAR(100):這個函數會接收一個參數,也就是每一列的部門名稱
  • RETURNS TABLE:函數的回傳方式是一張表——有回傳結果代表「可以看」,沒有回傳代表「過濾掉」
  • WITH SCHEMABINDING:把函數跟資料表綁定在一起,防止有人改了資料表結構導致函數壞掉
  • USER_NAME():取得目前登入的使用者名稱
  • WHERE @Department = USER_NAME():核心邏輯——只有當這一列的部門等於目前的使用者時,才回傳結果

這個函數的邏輯很簡單:如果資料列的 Department 欄位值等於目前登入的使用者名稱,就回傳結果(代表可以看);否則不回傳(代表過濾掉)。

第二步,建立 Security Policy,把函數綁到資料表上:

這段程式碼在做的事情是:

把上面建好的篩選函數,貼到 Employees 這張資料表上,並且立即啟用。

啟用之後,每當有人查詢 Employees,資料庫就會自動把每一列的 Department 欄位丟進函數裡判斷,決定要不要讓他看到這一列。

CREATE SECURITY POLICY dbo.EmployeeSecurityPolicy
ADD FILTER PREDICATE dbo.fn_SecurityPredicate(Department)
ON dbo.Employees
WITH (STATE = ON);

其中幾個關鍵字的意思:

  • CREATE SECURITY POLICY:建立一個安全性原則,這裡取名叫 EmployeeSecurityPolicy
  • ADD FILTER PREDICATE:指定要用哪個函數來做篩選,這裡用的就是剛才建好的 fn_SecurityPredicate
  • (Department):告訴函數「拿 Department 這個欄位的值去做判斷」
  • ON dbo.Employees:這個篩選規則要套用在 Employees 這張資料表上
  • WITH (STATE = ON):立即啟用;如果設成 OFF,規則會建立但不會生效,方便你先測試再上線

設定完之後,當 Engineering 這個使用者執行 SELECT * FROM Employees 時,他只會看到:

EmployeeIDNameDepartmentSalary
1AliceEngineering95000
3CarolEngineering88000
NameAlice
DepartmentEngineering
Salary95000
NameCarol
DepartmentEngineering
Salary88000

Marketing 的資料完全不會出現在結果裡,就像它們不存在一樣。

RLS 的限制與常見誤區

RLS 很強大,但它不是萬能的。

以下是幾個常見的誤區:

  • RLS 不管欄位。 就像開頭說的,RLS 只篩選列,不限制欄位。如果你需要隱藏某些欄位,要搭配 CLS。
  • RLS 可能影響效能。 因為每次查詢都要經過 Predicate Function 的計算,如果資料量很大、函數邏輯很複雜,查詢速度可能會變慢。建議在篩選欄位上建立索引。
  • sysadmin 和 db_owner 預設不受 RLS 限制。 如果你用這些高權限帳號測試 RLS,會發現怎麼設都沒效果——因為它們預設會繞過 Security Policy。
  • RLS 不會防止 Side-Channel 攻擊。 例如,使用者雖然看不到某一列的資料,但可能透過精心設計的查詢(例如利用錯誤訊息或執行時間差異)來推測出隱藏的資料。

Cell-Level Security——最細緻的控管,精確到每個格子

前面四層分別管「能不能連進資料庫」「能不能碰某張表」「能看哪些欄位」「能看哪些列」。

但有時候,你需要更細的控管——精確到某個「儲存格」。

舉個例子:同一張 Employees 資料表裡,你希望 HR 可以看到所有人的身分證字號,但其他人看到的是遮蔽後的版本(例如 A123******)。

注意,這裡不是把整個身分證字號(SSN)欄位隱藏(那是 CLS 的事),而是同一個欄位,不同人看到不同的值。

要做到這件事,在 SQL Server 裡有不同的做法,安全強度由低到高分成三個層級。

做法一:Dynamic Data Masking(DDM)——最輕量,但防君子不防小人

DDM(動態資料遮罩)是最容易設定的做法。

你可以想像成在紙上貼了一張便利貼把內容蓋住——資料本身還是完整地存在資料庫裡,只是查詢的時候「顯示」被蓋住了。

設定方式很簡單:

ALTER TABLE dbo.Employees
ALTER COLUMN SSN ADD MASKED WITH (FUNCTION = 'partial(1, "***-***-", 4)');

設定之後,沒有 UNMASK 權限的使用者查詢時會看到:

EmployeeIDNameSSN
1AliceA–-3456
2BobB–-7890
NameAlice
SSNA–-3456
NameBob
SSNB–-7890

而有 UNMASK 權限的使用者則看到完整的值,就像把便利貼撕掉一樣。

但 DDM 有一個重要的限制:它擋不住刻意的攻擊。

例如,使用者雖然看到的是 A***-***-3456,但他可以用 WHERE SSN = 'A123-456-3456' 去試,如果查得到結果,就知道這個值是對的。

所以 DDM 適合的場景是:防止一般使用者「不小心看到」敏感資料,例如客服人員打開畫面時不會直接看到客戶的完整身分證字號。

做法二:Always Encrypted——連資料庫自己都看不到原始值

如果你需要更強的保護,Always Encrypted 是把資料鎖進保險箱的做法。

資料在寫入資料庫之前就先加密,資料庫本身也看不到原始值。

只有持有加密金鑰的應用程式才能解密。

這代表就算有人直接存取資料庫檔案,或者就算是資料庫管理員(DBA),拿到的也是加密後的亂碼。

適合用在身分證字號、信用卡號這類高度敏感的資料。

做法三:Transparent Data Encryption(TDE)——防硬碟被偷

TDE 的做法不太一樣——它不是針對單一欄位或格子加密,而是把整個資料庫檔案加密。

這樣即使有人把伺服器的硬碟偷走,也無法直接讀取裡面的資料。

但跟 Always Encrypted 不同的是,只要你能正常登入資料庫,查詢時看到的還是明文。

所以 TDE 防的是「實體層面的資料外洩」,而不是「登入後的越權存取」。

三種做法的比較

機制安全強度防誰適用情境
DDM低防「不小心看到」客服畫面、一般報表
Always Encrypted高防資料庫管理員、防資料外洩身分證字號、信用卡號
TDE中防硬碟被偷整個資料庫的靜態加密
安全強度低
防誰防「不小心看到」
適用情境客服畫面、一般報表
安全強度高
防誰防資料庫管理員、防資料外洩
適用情境身分證字號、信用卡號
安全強度中
防誰防硬碟被偷
適用情境整個資料庫的靜態加密

這一層的重點是:當「欄位層級」的控管還不夠細,你需要精確控制每個格子的可見性時,就是 Cell-Level Security 上場的時候。 至於要用哪種做法,取決於你要防的是「不小心看到」還是「刻意竊取」。

五層安全性怎麼搭配?實務上的組合策略

了解了五層之後,最重要的問題是:在實務上,這五層該怎麼組合使用?

先用一張表整理每層的控管對象和適用情境:

層級控管對象適用情境常見搭配
Database Level資料庫連線與操作權限決定誰能進入資料庫所有場景的基礎
Object / Table Level資料表的 CRUD 權限限制使用者能碰哪些表搭配 Role 管理
Column-Level Security欄位的可見性隱藏薪水、個資等敏感欄位常與 RLS 搭配
Row-Level Security資料列的可見性多租戶、多部門的資料隔離常與 CLS 搭配
Cell-Level Security單一儲存格的值身分證字號、信用卡號的遮蔽搭配 DDM 或加密
控管對象資料庫連線與操作權限
適用情境決定誰能進入資料庫
常見搭配所有場景的基礎
控管對象資料表的 CRUD 權限
適用情境限制使用者能碰哪些表
常見搭配搭配 Role 管理
控管對象欄位的可見性
適用情境隱藏薪水、個資等敏感欄位
常見搭配常與 RLS 搭配
控管對象資料列的可見性
適用情境多租戶、多部門的資料隔離
常見搭配常與 CLS 搭配
控管對象單一儲存格的值
適用情境身分證字號、信用卡號的遮蔽
常見搭配搭配 DDM 或加密

實務情境:一個企業 ERP 系統

假設你在維護一個企業 ERP 系統,裡面有員工資料、訂單資料、財務資料等多張表。

以下是每一層各自負責的事情:

  • Database Level:外包廠商的帳號只能連進測試資料庫,不能碰正式環境。
  • Object / Table Level:業務部門只能查 Orders 和 Customers 表,不能碰 Employees 和 Financials 表。
  • Column-Level Security:HR 可以看到 Employees 表的所有欄位,但部門經理看不到 Salary 和 SSN。
  • Row-Level Security:北區經理只能看到北區的訂單資料,南區經理只能看到南區的。
  • Cell-Level Security:所有人查 Customers 表時,信用卡號欄位都會被遮罩,只有財務部門有 UNMASK 權限。

這五層各管各的,彼此不衝突,也不能互相取代。

缺少任何一層,都會在安全性上留下缺口。

常見問題整理

RLS 會影響效能嗎?

會,但通常影響不大。

RLS 的 Predicate Function 會在每次查詢時被執行,相當於在每個查詢後面自動加了一個 WHERE 條件。

如果你的篩選欄位上有索引,效能影響通常可以忽略。

但如果 Predicate Function 的邏輯很複雜(例如要去查另一張表),或是資料量非常大,就需要注意效能調校。

建議做法:在 Predicate Function 使用的欄位上建立索引,並用執行計畫(Execution Plan)確認查詢效能。

CLS 和 View 有什麼不同?

兩者都能限制使用者看到的欄位,但控管的嚴格程度不同。

View 是建立一個虛擬的窗口,只暴露你想讓使用者看到的欄位。

但如果使用者同時擁有原始資料表的 SELECT 權限,他可以直接查原表,繞過 View 的限制。

CLS 則是在資料表層級直接控管欄位的存取權限,就算使用者知道欄位名稱,沒有權限就是查不到。

在需要嚴格控管的場景下,CLS 比 View 更可靠。

只用 RLS 夠不夠?

不夠。

RLS 只管「哪些列可以看到」,但不管「哪些欄位可以看到」「哪些表可以碰」「能不能連進資料庫」。

如果你只設了 RLS,使用者雖然只能看到自己部門的資料列,但可能看到所有的欄位(包括薪水、個資等敏感資訊)。

安全性是一個整體。

五層防線各有各的職責,少了任何一層都會有缺口。

根據你的需求,至少把前四層(Database、Table、Column、Row)都設定好,Cell-Level 則視資料的敏感程度決定是否啟用。

目前還沒有留言,成為第一個留言的人吧!

發表留言

留言將在審核後顯示。

資料庫

目錄

  • 資料庫安全性的五個層級——先看全貌
  • Database Level——你連門都進不去
  • Object / Table Level——進了門,但哪些房間能進?
  • Column-Level Security(CLS)——同一張表,你只能看特定欄位
  • Row-Level Security(RLS)——同一張表,你只能看特定資料列
  • RLS 的核心概念:Security Policy + Predicate Function
  • 實作範例:多部門的員工資料表
  • RLS 的限制與常見誤區
  • Cell-Level Security——最細緻的控管,精確到每個格子
  • 做法一:Dynamic Data Masking(DDM)——最輕量,但防君子不防小人
  • 做法二:Always Encrypted——連資料庫自己都看不到原始值
  • 做法三:Transparent Data Encryption(TDE)——防硬碟被偷
  • 三種做法的比較
  • 五層安全性怎麼搭配?實務上的組合策略
  • 實務情境:一個企業 ERP 系統
  • 常見問題整理
  • RLS 會影響效能嗎?
  • CLS 和 View 有什麼不同?
  • 只用 RLS 夠不夠?