ใช้การรักษาความปลอดภัยระดับแถว
Row-Level Security (RLS) ช่วยให้คุณสามารถควบคุมการเข้าถึงแถวในตารางฐานข้อมูลโดยยึดตามลักษณะของผู้ใช้ที่ดําเนินการแบบสอบถาม ซึ่งแตกต่างจากสิทธิ์ระดับตารางที่ให้หรือปฏิเสธการเข้าถึงทั้งตาราง RLS จะกรองแถวแบบไดนามิกเพื่อให้ผู้ใช้เห็นเฉพาะข้อมูลที่ได้รับอนุญาตให้เข้าถึงเท่านั้น
ความสามารถนี้มีประโยชน์เมื่อผู้ใช้หรือผู้เช่าหลายคนใช้ตารางเดียวกัน แต่ควรดูเฉพาะข้อมูลของตนเองเท่านั้น ตัวแทนฝ่ายขายจะเห็นบันทึกลูกค้าของตนเอง ผู้จัดการจะเห็นข้อมูลของทีม และผู้อํานวยการระดับภูมิภาคจะเห็นข้อมูลทั้งหมดสําหรับภูมิภาคของตน การกรองจะเกิดขึ้นโดยอัตโนมัติโดยไม่ต้องเปลี่ยนรหัสแอปพลิเคชัน
ทําความเข้าใจส่วนประกอบ RLS
Row-Level Security ใช้สององค์ประกอบที่ทํางานร่วมกัน: เพรดิเคตความปลอดภัยและนโยบายความปลอดภัย
เพรดิเคตความปลอดภัยคือฟังก์ชันที่มีค่าตารางแบบอินไลน์ที่ส่งกลับ 1 (จริง) หรือ 0 (เท็จ) สําหรับแต่ละแถว โดยจะรับบริบทของผู้ใช้ปัจจุบันและค่าแถว จากนั้นจะตัดสินใจว่าผู้ใช้รายนั้นควรเห็นแถวหรือไม่ คิดว่ามันเป็นตรรกะทางธุรกิจของคุณสําหรับการเข้าถึงข้อมูล ซึ่งบรรจุเป็นฟังก์ชัน
นโยบายความปลอดภัยจะผูกฟังก์ชันเพรดิเคตของคุณกับตารางและระบุชนิดของการกรอง คุณสามารถสร้างเพ รดิเคตตัวกรอง ที่แยกแถวที่ไม่ได้รับอนุญาตออกจากผลลัพธ์แบบสอบถามอย่างเงียบๆ หรือ บล็อกเพรดิเคต ที่ป้องกันการแทรก ปรับปรุง และลบโดยไม่ได้รับอนุญาต
ตัวกรองเพรดิเคตมีผล SELECTและUPDATEDELETEคําสั่งโดยการลบแถวที่ผู้ใช้ไม่สามารถเข้าถึงได้ ผู้ใช้จะไม่ได้รับข้อผิดพลาด แต่จะเห็นเพียงชุดผลลัพธ์ที่กรองแล้ว เพรดิเคตบล็อกทํางานต่างกัน: ทําให้เกิดข้อผิดพลาดเมื่อผู้ใช้พยายามเปลี่ยนแปลงโดยไม่ได้รับอนุญาต
สร้างเพรดิเคตตัวกรอง
เริ่มต้นด้วยการสร้างฟังก์ชันเพรดิเคตที่ประเมินการเข้าถึงแถว ฟังก์ชันยอมรับพารามิเตอร์ที่แสดงค่าคอลัมน์เพื่อตรวจสอบและส่งคืนตารางที่มีแถวเดียวเมื่ออนุญาตให้เข้าถึง
นี่คือสถานการณ์ทั่วไป: แอปพลิเคชันแบบหลายผู้เช่าที่แต่ละแถวมี TenantID คอลัมน์:
CREATE SCHEMA Security;
GO
CREATE FUNCTION Security.fn_TenantAccessPredicate(@TenantID int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_TenantAccessPredicate_Result
WHERE @TenantID = CAST(SESSION_CONTEXT(N'TenantID') AS int);
ฟังก์ชันนี้จะตรวจสอบว่าแถว TenantID ตรงกับค่าที่เก็บไว้ในบริบทของเซสชันหรือไม่ แอปพลิเคชันของคุณตั้งค่าบริบทนี้หลังจากที่ผู้ใช้ตรวจสอบสิทธิ์:
EXEC sp_set_session_context @key = N'TenantID', @value = 42;
สําหรับสถานการณ์ที่อิงตามผู้ใช้ฐานข้อมูลมากกว่าบริบทของเซสชัน เพรดิเคตสามารถอ้างอิงผู้ใช้ปัจจุบันได้:
CREATE FUNCTION Security.fn_SalesRepPredicate(@SalesRepID int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_SalesRepPredicate_Result
WHERE @SalesRepID = DATABASE_PRINCIPAL_ID()
OR IS_MEMBER('SalesManagers') = 1;
เพรดิเคตนี้ช่วยให้ตัวแทนฝ่ายขายสามารถดูเรกคอร์ดของตนเองในขณะที่ผู้จัดการในบทบาทสามารถ SalesManagers ดูเรกคอร์ดทั้งหมดได้
สร้างนโยบายความปลอดภัย
เมื่อคุณกําหนดฟังก์ชันเพรดิเคตของคุณแล้ว ให้สร้างนโยบายความปลอดภัยที่นําไปใช้กับตาราง:
CREATE SECURITY POLICY TenantSecurityPolicy
ADD FILTER PREDICATE Security.fn_TenantAccessPredicate(TenantID)
ON dbo.Orders,
ADD FILTER PREDICATE Security.fn_TenantAccessPredicate(TenantID)
ON dbo.OrderDetails
WITH (STATE = ON);
นโยบายนี้กรองทั้งตาราง Orders และ OrderDetails ตารางโดยใช้ฟังก์ชันเพรดิเคตเดียวกัน เมื่อผู้ใช้คิวรีตารางเหล่านี้ พวกเขาจะเห็นเฉพาะแถวที่ตรงกับบริบทของผู้เช่าเท่านั้น
คุณสามารถรวมตัวกรองและบล็อกเพรดิเคตไว้ในนโยบายเดียว:
CREATE SECURITY POLICY SalesSecurityPolicy
ADD FILTER PREDICATE Security.fn_SalesRepPredicate(SalesRepID)
ON dbo.CustomerAccounts,
ADD BLOCK PREDICATE Security.fn_SalesRepPredicate(SalesRepID)
ON dbo.CustomerAccounts AFTER INSERT,
ADD BLOCK PREDICATE Security.fn_SalesRepPredicate(SalesRepID)
ON dbo.CustomerAccounts AFTER UPDATE
WITH (STATE = ON);
ทําไมต้องเพิ่มเพรดิเคตบล็อก? หากไม่มีพวกเขา ผู้ใช้สามารถแทรกแถวที่มีแถวอื่น SalesRepID และสูญเสียการเข้าถึงข้อมูลที่เพิ่งสร้างขึ้น เพรดิเคตบล็อกช่วยให้แน่ใจว่าผู้ใช้สามารถแทรกหรืออัปเดตได้เฉพาะแถวที่พวกเขาสามารถดูได้
ใช้รูปแบบการเข้าถึงแบบลําดับชั้น
หลายองค์กรต้องการการเข้าถึงข้อมูลแบบลําดับชั้น ผู้จัดการควรดูข้อมูลของผู้ใต้บังคับบัญชา คุณสามารถนําไปใช้ได้โดยการรวม RLS กับตารางลําดับชั้นการจัดการ
นี่คือฟังก์ชันที่ข้ามลําดับชั้น:
CREATE FUNCTION Security.fn_HierarchyPredicate(@OwnerID int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
WITH EmployeeHierarchy AS (
SELECT EmployeeID, ManagerID
FROM dbo.Employees
WHERE EmployeeID = DATABASE_PRINCIPAL_ID()
UNION ALL
SELECT e.EmployeeID, e.ManagerID
FROM dbo.Employees e
INNER JOIN EmployeeHierarchy h ON e.ManagerID = h.EmployeeID
)
SELECT 1 AS fn_HierarchyPredicate_Result
WHERE @OwnerID IN (SELECT EmployeeID FROM EmployeeHierarchy);
นิพจน์ตารางทั่วไปแบบเรียกซ้ํา (CTE) นี้สร้างห่วงโซ่ทั้งหมดของพนักงานที่รายงานไปยังผู้ใช้ปัจจุบัน เพรดิเคตอนุญาตให้เข้าถึงแถวใดๆ ที่เป็นเจ้าของโดยบุคคลในลําดับชั้นนั้น
Note
เพรดิเคตแบบเรียกซ้ําอาจส่งผลต่อประสิทธิภาพของคิวรีในชุดข้อมูลขนาดใหญ่ พิจารณาการแคชความสัมพันธ์ตามลําดับชั้นหรือจํากัดความลึกของการเรียกซ้ําเพื่อประสิทธิภาพที่ดีขึ้น
จัดการนโยบายความปลอดภัย
นโยบายความปลอดภัยสนับสนุนการดําเนินการจัดการหลายอย่างสําหรับการบํารุงรักษาอย่างต่อเนื่อง คุณปิดใช้นโยบายชั่วคราวได้โดยไม่ต้องนําออกดังนี้
ALTER SECURITY POLICY TenantSecurityPolicy
WITH (STATE = OFF);
คุณสามารถเพิ่มเพรดิเคตใหม่ให้กับนโยบายที่มีอยู่ได้ดังนี้
ALTER SECURITY POLICY TenantSecurityPolicy
ADD FILTER PREDICATE Security.fn_TenantAccessPredicate(TenantID)
ON dbo.Shipments;
หรือลบเพรดิเคตเมื่อตารางไม่ต้องการการกรองอีกต่อไป:
ALTER SECURITY POLICY TenantSecurityPolicy
DROP FILTER PREDICATE ON dbo.Orders;
เมื่อต้องการดูนโยบายความปลอดภัยที่มีอยู่และเพรดิเคท:
SELECT p.name AS PolicyName,
p.is_enabled,
o.name AS TableName,
pred.predicate_definition
FROM sys.security_policies p
INNER JOIN sys.security_predicates pred ON p.object_id = pred.object_id
INNER JOIN sys.objects o ON pred.target_object_id = o.object_id;
เคล็ดลับ
เมื่อแก้ไขปัญหา RLS ให้ปิดใช้งานนโยบายความปลอดภัยชั่วคราวและเปรียบเทียบผลลัพธ์ ซึ่งจะช่วยระบุว่าผลลัพธ์ที่ไม่คาดคิดมาจากเพรดิเคต RLS หรือตรรกะแบบสอบถามอื่นๆ หรือไม่
Row-Level Security ทํางานอย่างโปร่งใสกับมุมมอง กระบวนงานที่เก็บไว้ และวัตถุฐานข้อมูลอื่นๆ ที่สอบถามตารางที่ได้รับการป้องกัน ผู้ใช้ที่เข้าถึงข้อมูลผ่านเส้นทางใดๆ จะได้รับผลลัพธ์ที่กรองอย่างสม่ําเสมอตามบริบทด้านความปลอดภัย