Bảo mật

Phân quyền bảng lương: lọc dòng, che cột, và chỗ che cột không đủ

Dựng phân quyền bảng lương 2.400 nhân viên trên SQL Server, rồi đo số truy vấn khôi phục đúng lương một người bị che bằng data masking.

Mục lục
  1. 1. Báo cáo lương và ba vai trò
  2. 2. Row-level security: lọc dòng theo cửa hàng
  3. 3. Che cột lương, và chỗ che cột không đủ
  4. 4. Cách đúng cho kế toán: chỉ trả tổng theo cửa hàng
  5. 5. Ghi vết ai xem lương
  6. Những chỗ hay hiểu sai
  7. Đọc tiếp
  8. Nguồn

9:12 sáng thứ Hai, quản lý cửa hàng 3 của BanHang mở báo cáo lương trong ERP. Màn hình liệt kê 20 nhân viên cửa hàng mình, kèm lương tháng 9. Người quản lý đó tò mò lương của một quản lý cửa hàng khác, và của một trưởng phòng ở hội sở. Trong ứng dụng không có nút nào mở ra con số đó. Câu hỏi của bài này: nếu người đó có thêm một quyền đọc cơ sở dữ liệu, các lớp phân quyền trên BanHang chặn được tới đâu. Bài dựng bảng lương 2.400 nhân viên trên SQL Server, đi qua row-level security, dynamic data masking, rồi đo một truy vấn suy luận khôi phục đúng lương một người dù cột đã bị che.

Đọc nhanh

  • Row-level security lọc dòng theo cửa hàng, ngữ cảnh người dùng truyền qua SESSION_CONTEXT đặt read-only. Predicate phải gắn lên từng bảng: chỉ gắn lên bảng nhân viên thì quản lý cửa hàng 3 vẫn đọc 2.400 dòng lương; gắn cả bảng lương thì còn 20.
  • Dynamic data masking che cột lương cho vai trò kế toán, nhưng người chạy được truy vấn ad hoc suy ra giá trị thật bằng WHERE tìm nhị phân. Đo được: 26 truy vấn khôi phục đúng lương một người trên dải 8–56 triệu. Microsoft ghi rõ giới hạn này trong tài liệu.
  • Cách đúng cho kế toán: không cấp quyền đọc bảng chi tiết; cho gọi thủ tục chạy EXECUTE AS OWNER chỉ trả tổng theo cửa hàng, kèm ngưỡng số người tối thiểu. Truy vấn suy luận khi đó bị từ chối ngay ở quyền SELECT.
  • SQL Server Audit ghi vết ai xem lương, chạy được cả trên Express/LocalDB. Audit EXECUTE thủ tục mới truy ra người thật, vì SELECT bên trong module chạy dưới chủ sở hữu.

Mọi số đo trong bài chạy trên SQL Server 2019 Express qua LocalDB (15.0.4382.1, CU27), database riêng Kumeo_bangluong. Hạt giống của dữ liệu là chính NhanVienId, nên chạy lại cho cùng con số. Lương và phân bố nhân viên là minh họa.

1. Báo cáo lương và ba vai trò

Bảng lương có hai bảng: nhân viên gắn với một cửa hàng, và dòng lương mỗi tháng. Đây là hai bảng mới của BanHang, mảng nhân sự.

CREATE TABLE dbo.NhanVien (
    NhanVienId int NOT NULL CONSTRAINT PK_NhanVien PRIMARY KEY CLUSTERED,
    CuaHangId  int NOT NULL,                 -- cửa hàng hiện tại
    HoTen      nvarchar(100) NOT NULL,
    ChucDanh   nvarchar(50) NOT NULL
);

CREATE TABLE dbo.BangLuong (
    Ky         date NOT NULL,
    NhanVienId int  NOT NULL,
    CuaHangId  int  NOT NULL,                -- cửa hàng trong kỳ này
    LuongThang decimal(18, 2) NOT NULL,
    CONSTRAINT PK_BangLuong PRIMARY KEY CLUSTERED (Ky, NhanVienId)
);

BangLuong.CuaHangId không phải bản chép thừa của NhanVien.CuaHangId. Nó ghi nhân viên thuộc cửa hàng nào trong kỳ lương đó. Nhân viên chuyển từ cửa hàng 3 sang cửa hàng 5 tháng 10 thì dòng lương tháng 9 vẫn thuộc cửa hàng 3: quản lý cửa hàng 3 vẫn xem được kỳ cũ, quản lý cửa hàng 5 chỉ thấy từ kỳ mới. Cột này cũng là thứ mục 2 dùng để lọc dòng lương.

Lương dùng decimal(18, 2) như mọi cột tiền của BanHang. Dữ liệu minh họa: 2.400 nhân viên trên 120 cửa hàng, lương tháng 9/2026 trải đều 8.000.000 đến 56.000.000 đồng. Để cho thấy một tình huống ở mục 4, phân bố cố ý không đều: cửa hàng 1 có 36 người, cửa hàng 2 đến 119 mỗi nơi 20 người, cửa hàng 120 mới khai trương chỉ 4 người.

Ba vai trò, ba phần dữ liệu được thấy:

Cùng một bảng lương, ba vai trò thấy ba phần khác nhau Quản lý cửa hàng 3 Lọc dòng (RLS) NV cửa hàng 3 · lương thật 20 dòng, chỉ cửa hàng 3 Cửa hàng khác: không thấy CH3 · NV66 · 14.912.114 CH3 · NV73 · 15.645.217 CH5 · ... · ✕ ẩn CH66 · ... · ✕ ẩn Phòng nhân sự Toàn quyền đọc (UNMASK) Mọi NV · lương thật 2.400 dòng, 120 cửa hàng Thấy lương từng người CH3 · NV73 · 15.645.217 CH65 · NV1313 · 49.509.175 CH75 · NV1500 · 21.093.497 ... 2.400 dòng Kế toán Chỉ tổng theo cửa hàng SUM theo cửa hàng Không có lương cá nhân Ngưỡng ≥ 5 người/cửa hàng CH1 (36) · 357.749.514 CH2 (20) · 257.397.970 CH3 (20) · 299.289.570 CH120 (4) · ✕ ẩn
Cùng một bảng lương, ba vai trò thấy ba phần khác nhau.

Ba vai trò là ba database principal riêng: nhansu, ketoan, quanly. Vai trò quyết định được ở mức database. Chiều "cửa hàng nào" của quản lý thì không: mọi quản lý đều là principal quanly, nên cửa hàng cụ thể phải đến từ ngữ cảnh phiên do ứng dụng đặt.

CREATE USER nhansu WITHOUT LOGIN;   -- nhân sự: xem tất cả, lương thật
CREATE USER ketoan WITHOUT LOGIN;   -- kế toán: chỉ tổng theo cửa hàng
CREATE USER quanly WITHOUT LOGIN;   -- quản lý: chỉ cửa hàng mình

2. Row-level security: lọc dòng theo cửa hàng

Row-level security (RLS, lọc dòng ở tầng database) gắn một predicate vào bảng. Predicate là một hàm trả bảng nội tuyến (inline table-valued function): trả một dòng nghĩa là "cho xem", trả rỗng nghĩa là "lọc đi". Hàm lấy cửa hàng của phiên từ SESSION_CONTEXT, một túi khóa–giá trị gắn với kết nối. RLS có từ SQL Server 2016.

CREATE SCHEMA baomat;
GO
CREATE FUNCTION baomat.fn_loc_cuahang(@CuaHangId AS int)
    RETURNS TABLE
    WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS cho_phep
    WHERE USER_NAME() IN (N'nhansu', N'ketoan', N'dbo')             -- nhân sự, kế toán: toàn bộ
       OR CAST(SESSION_CONTEXT(N'CuaHangId') AS int) = @CuaHangId;  -- quản lý: cửa hàng trong phiên
GO
CREATE SECURITY POLICY baomat.pol_bangluong
    ADD FILTER PREDICATE baomat.fn_loc_cuahang(CuaHangId) ON dbo.NhanVien,
    ADD FILTER PREDICATE baomat.fn_loc_cuahang(CuaHangId) ON dbo.BangLuong
    WITH (STATE = ON);

Một hàm, gắn lên hai bảng. Predicate chỉ lọc đúng bảng nó được gắn: RLS không đi theo phép join hay theo khóa ngoại sang bảng khác. Bản đầu của bài này chỉ gắn lên dbo.NhanVien, trong khi quanly có SELECT và UNMASK trên dbo.BangLuong. Kết quả đo: quản lý cửa hàng 3 đếm NhanVien ra 20, nhưng đếm BangLuong ra 2.400 dòng của 120 cửa hàng, và đọc được lương thật 49.509.175 của NV1313 ở cửa hàng 65. Mỗi bảng chứa dữ liệu cần lọc phải có predicate riêng.

Nhánh USER_NAME() cho nhân sự và kế toán thấy mọi cửa hàng; nhánh SESSION_CONTEXT lọc quản lý theo cửa hàng. Nếu ngữ cảnh chưa đặt, SESSION_CONTEXT trả NULL, phép so sánh ra NULL, dòng bị lọc. Mặc định là đóng. Đặt predicate trong schema riêng baomat để tách quyền trên hàm khỏi bảng đích; với SCHEMABINDING = ON, người dùng không cần quyền SELECT trên hàm vẫn truy vấn được bảng.

Ứng dụng mở kết nối bằng một login ít quyền, đặt ngữ cảnh sau khi người dùng đăng nhập, rồi mới truy vấn. Cờ @read_only = 1 khóa khóa đó đến khi kết nối đóng (trả về pool), để một request sau trên cùng kết nối không đổi được cửa hàng.

sequenceDiagram
  participant U as Quản lý CH3
  participant App as App ERP
  participant DB as SQL Server
  U->>App: Đăng nhập
  App->>DB: Mở kết nối (login app, ít quyền)
  App->>DB: sp_set_session_context 'CuaHangId'=3, read_only=1
  App->>DB: SELECT ... FROM dbo.BangLuong
  DB->>DB: RLS gọi fn_loc_cuahang(CuaHangId)
  DB-->>App: Chỉ 20 dòng lương cửa hàng 3

Phép thử: đặt cửa hàng 3 rồi đọc dưới ngữ cảnh quanly, và đọc dưới ngữ cảnh nhansu.

EXEC sys.sp_set_session_context @key = N'CuaHangId', @value = 3;
EXECUTE AS USER = 'quanly';
SELECT (SELECT COUNT(*) FROM dbo.NhanVien) AS nhan_vien,
       (SELECT COUNT(*) FROM dbo.BangLuong) AS dong_luong,
       (SELECT COUNT(DISTINCT CuaHangId) FROM dbo.BangLuong) AS so_ch,
       (SELECT LuongThang FROM dbo.BangLuong WHERE NhanVienId = 1313) AS luong_nv1313;
REVERT;
EXECUTE AS USER = 'nhansu';
SELECT (SELECT COUNT(*) FROM dbo.NhanVien) AS nhan_vien,
       (SELECT COUNT(*) FROM dbo.BangLuong) AS dong_luong,
       (SELECT COUNT(DISTINCT CuaHangId) FROM dbo.BangLuong) AS so_ch,
       (SELECT LuongThang FROM dbo.BangLuong WHERE NhanVienId = 1313) AS luong_nv1313;
REVERT;

Bảng dưới gộp thêm hai lần đo cùng câu lệnh: lúc policy mới gắn lên NhanVien (gỡ bằng ALTER SECURITY POLICY ... DROP FILTER PREDICATE ON dbo.BangLuong), và lúc phiên chưa đặt CuaHangId.

Ngữ cảnh nhan_vien dong_luong so_ch luong_nv1313
quanly, CuaHangId = 3, policy chỉ trên NhanVien 20 2.400 120 49.509.175
quanly, CuaHangId = 3, policy trên cả hai bảng 20 20 1 NULL
quanly, chưa đặt ngữ cảnh 0 0 0 NULL
nhansu 2.400 2.400 120 49.509.175

Filter predicate lọc im lặng: ứng dụng không biết có dòng bị bỏ, kết quả chỉ ngắn đi. Nhưng RLS ở đây tin rằng SESSION_CONTEXT do ứng dụng đặt đúng. Người chỉ truy cập qua ứng dụng không đặt lại được (khóa read-only). Người có kết nối SQL trực tiếp thì tự đặt ngữ cảnh của mình, kể cả đặt CuaHangId của cửa hàng khác. RLS theo ngữ cảnh phiên là kiểm soát trong một ứng dụng dùng chung bảng, không thay cho việc cấp quyền tối thiểu cho từng người.

3. Che cột lương, và chỗ che cột không đủ

Kế toán cần nhìn bảng lương để đối soát, nhưng không được xem lương từng người. Dynamic data masking (DDM, che dữ liệu động, có từ SQL Server 2016) đổi cách một cột hiện với người không có quyền UNMASK. Dữ liệu trong bảng không đổi; chỉ kết quả truy vấn bị che.

ALTER TABLE dbo.BangLuong
    ALTER COLUMN LuongThang ADD MASKED WITH (FUNCTION = 'default()');
GRANT SELECT ON dbo.BangLuong TO nhansu, quanly, ketoan;  -- cấp SELECT cho kế toán là thiết kế SAI
GRANT UNMASK TO nhansu;   -- nhân sự xem lương thật
GRANT UNMASK TO quanly;   -- quản lý xem lương thật của cửa hàng mình
-- ketoan KHÔNG có UNMASK

Hàm default() che số bằng 0. Dưới ngữ cảnh ketoan, cột LuongThang trả .00; dưới nhansu, trả số thật. Trên SQL Server 2019, UNMASK chỉ cấp ở mức database. Quyền UNMASK mịn theo schema, bảng hay cột là tính năng của SQL Server 2022.

Vấn đề: default() che số bằng 0 khi trả kết quả, nhưng điều kiện WHERE vẫn chạy trên giá trị thật. Kế toán không đọc thẳng được lương, nhưng dò được bằng một dãy câu hỏi có/không. Hỏi "lương người này có ≤ X không" bằng cách xem truy vấn có trả dòng hay không, rồi tìm nhị phân.

-- Chạy dưới ngữ cảnh ketoan. Mỗi vòng lặp là một truy vấn ad hoc.
DECLARE @lo bigint = 8000000, @hi bigint = 56000000, @mid bigint, @n int = 0;
WHILE @lo < @hi
BEGIN
    SET @mid = (@lo + @hi) / 2;
    SET @n += 1;
    IF EXISTS (SELECT 1 FROM dbo.BangLuong WHERE NhanVienId = 1313 AND LuongThang <= @mid)
        SET @hi = @mid;
    ELSE
        SET @lo = @mid + 1;
END
SELECT @lo AS luong_khoi_phuc, @n AS so_truy_van;  -- 49509175.00, 26

Lương cột hiện ra là 0, nhưng WHERE LuongThang <= @mid lọc theo giá trị thật, nên sau 26 truy vấn @lo hội tụ về đúng 49.509.175 đồng. Thử ba người khác nhau trên cùng dải 8–56 triệu, cả ba đều cần đúng 26 truy vấn: NV73 ra 15.645.217, NV1313 ra 49.509.175, NV1500 ra 21.093.497. Số truy vấn bằng số bit của khoảng cần dò, tức khoảng log2(độ rộng): khoảng càng hẹp (kẻ dò càng biết trước) thì càng ít.

Khôi phục đúng lương một người: số truy vấn ≈ log2(độ rộng khoảng dò)

Khoảng 1 triệu20 lầnKhoảng 10 triệu24 lầnCả dải 8–56 triệu26 lầnDải 0–64 triệu26 lần
Đo thật trên LocalDB 2019 (15.0.4382), ngữ cảnh kế toán không có UNMASK, cột lương che bằng default(). Khôi phục đúng lương NV1313 = 49.509.175 đồng tới từng đồng.
Bảng số liệu
Giá trị
Khoảng 1 triệu20 lần
Khoảng 10 triệu24 lần
Cả dải 8–56 triệu26 lần
Dải 0–64 triệu26 lần

Đây không phải lỗi cài đặt. Microsoft ghi thẳng trong tài liệu: "Dynamic data masking không nhằm chặn người dùng kết nối trực tiếp và chạy các truy vấn vét cạn để lộ từng phần dữ liệu nhạy cảm", và nêu đúng ví dụ dò lương bằng WHERE Salary > 99999 and Salary < 100001. RLS cũng có kênh phụ tương tự khi người dùng chạy được truy vấn tùy ý: tài liệu dẫn SELECT 1/(SALARY-100000) FROM PAYROLL WHERE NAME='John Doe', lỗi chia cho 0 cho biết lương đúng bằng 100.000. Cả hai là lớp giảm phơi bày cho ứng dụng, không phải ranh giới chống người có quyền đọc và quyền chạy truy vấn ad hoc.

4. Cách đúng cho kế toán: chỉ trả tổng theo cửa hàng

Gốc của lỗ hổng ở mục 3 là cấp cho kế toán quyền SELECT trên bảng chi tiết. Bỏ quyền đó đi. Kế toán chỉ cần tổng theo cửa hàng, nên cho họ một đường đọc chỉ trả tổng.

REVOKE SELECT ON dbo.BangLuong FROM ketoan;
REVOKE SELECT ON dbo.NhanVien FROM ketoan;

Một cái bẫy: view gộp sẵn vẫn không đủ. Che cột đi theo ngữ cảnh người gọi, và SUM trên một cột đã che cũng bị che. Kế toán đọc view tổng vẫn ra .00, dù ownership chaining đã cho qua phần quyền SELECT.

CREATE VIEW baomat.v_TongLuongCuaHang AS
    SELECT Ky, CuaHangId, COUNT(*) AS SoNhanVien, SUM(LuongThang) AS TongLuong
    FROM dbo.BangLuong
    GROUP BY Ky, CuaHangId HAVING COUNT(*) >= 5;
-- ketoan SELECT view -> TongLuong = .00 vì mask theo ngữ cảnh ketoan

Cách chạy được là một thủ tục WITH EXECUTE AS OWNER: phần thân chạy dưới ngữ cảnh chủ sở hữu (dbo, có UNMASK), nên SUM tính trên lương thật, và chỉ con số tổng rời khỏi thủ tục. Kế toán chỉ được EXECUTE, không chạm bảng chi tiết. Ownership chaining lo phần quyền trên bảng, nên không phải cấp SELECT cho kế toán.

CREATE PROCEDURE baomat.usp_TongLuongCuaHang
    @Ky date
WITH EXECUTE AS OWNER
AS
BEGIN
    SET NOCOUNT ON;
    SELECT CuaHangId, COUNT(*) AS SoNhanVien, SUM(LuongThang) AS TongLuong
    FROM dbo.BangLuong
    WHERE Ky = @Ky
    GROUP BY CuaHangId
    HAVING COUNT(*) >= 5   -- ẩn cửa hàng dưới 5 người
    ORDER BY CuaHangId;
END
GO
GRANT EXECUTE ON baomat.usp_TongLuongCuaHang TO ketoan;

Thủ tục gộp theo BangLuong.CuaHangId của từng kỳ, nên tổng tháng 9 của cửa hàng 3 không đổi khi tháng 10 có người chuyển đi. Bên trong thủ tục, USER_NAME() là dbo, nằm trong nhánh "toàn bộ" của predicate, nên RLS không lọc mất cửa hàng nào. Quản lý gọi thủ tục này thì bị từ chối (The EXECUTE permission was denied), vì chỉ ketoan được cấp.

Mệnh đề HAVING COUNT(*) >= 5 là ngưỡng số người tối thiểu. Một cửa hàng chỉ có một người thì tổng bằng chính lương người đó; cửa hàng hai ba người thì ai biết lương mình đã suy ra được lương đồng nghiệp. Ngưỡng chặn kênh suy luận này. Trong dữ liệu minh họa, cửa hàng 120 có 4 người nên bị ẩn; kế toán thấy 119 cửa hàng.

EXEC baomat.usp_TongLuongCuaHang @Ky = '2026-09-01' dưới ngữ cảnh ketoan:
CuaHangId   SoNhanVien  TongLuong
1           36          357749514.00
2           20          257397970.00
3           20          299289570.00
... 119 cửa hàng, cửa hàng 120 (4 người) bị ẩn

Phép thử lại truy vấn suy luận ở mục 3, vẫn dưới ngữ cảnh ketoan:

SELECT 1 FROM dbo.BangLuong WHERE NhanVienId = 1313 AND LuongThang <= 50000000;
-- Msg 229: The SELECT permission was denied on the object 'BangLuong'

Không còn quyền SELECT trên bảng chi tiết, dãy câu hỏi có/không ở mục 3 dừng ngay ở câu đầu. Kế toán vẫn đối soát được tổng, không còn đường chạm tới lương từng người.

5. Ghi vết ai xem lương

Nhân sự và quản lý vẫn xem được lương thật, đúng vai trò. Cần biết ai đã xem gì, lúc nào. SQL Server Audit ghi vết việc đọc. Mọi edition đều có server audit; database audit có trên mọi edition từ SQL Server 2016 SP1. Kiểm trên LocalDB (nền Express): cả server audit ghi ra file lẫn database audit specification đều chạy, và fn_get_audit_file đọc lại được.

CREATE SERVER AUDIT AU_bangluong
    TO FILE (FILEPATH = 'D:\Audit\')
    WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE);
ALTER SERVER AUDIT AU_bangluong WITH (STATE = ON);
GO
CREATE DATABASE AUDIT SPECIFICATION DAS_bangluong
    FOR SERVER AUDIT AU_bangluong
    ADD (SELECT ON dbo.BangLuong BY public),
    ADD (EXECUTE ON baomat.usp_TongLuongCuaHang BY public)
    WITH (STATE = ON);

Audit tạo ra ở trạng thái tắt, phải STATE = ON mới ghi. Với ON_FAILURE = CONTINUE, lỗi ghi audit không làm dừng lệnh; đổi sang SHUTDOWN thì một lỗi ghi audit sẽ dừng cả instance, chỉ dùng khi quy định bắt buộc không được mất một dòng vết nào. Sau khi nhân sự xem lương một người, quản lý cửa hàng 3 chạy SELECT NhanVienId, LuongThang FROM dbo.BangLuong (không WHERE, RLS trả 20 dòng), và kế toán gọi thủ tục tổng, đọc lại file:

SELECT event_time, action_id, database_principal_name AS nguoi, object_name,
       CONVERT(varchar(80), statement) AS cau_lenh
FROM sys.fn_get_audit_file('D:\Audit\AU_bangluong*.sqlaudit', DEFAULT, DEFAULT)
WHERE action_id IN ('SL', 'EX') ORDER BY event_time;
nguoi action object câu lệnh (rút gọn)
nhansu SL BangLuong SELECT LuongThang FROM dbo.BangLuong WHERE NhanVienId = 1313
quanly SL BangLuong SELECT NhanVienId, LuongThang FROM dbo.BangLuong
ketoan EX usp_TongLuongCuaHang EXEC baomat.usp_TongLuongCuaHang @Ky = '2026-09-01'
dbo SL BangLuong SELECT CuaHangId, COUNT(*) AS SoNhanVien, SUM(LuongThang)...

Audit ghi cả tên người và nguyên văn câu lệnh. Lưu ý dòng cuối: lệnh SELECT bên trong thủ tục được ghi dưới dbo, vì thân thủ tục chạy EXECUTE AS OWNER. Muốn truy ra con người đã xem tổng thì phải audit chính hành vi EXECUTE thủ tục, nơi ghi đúng ketoan. Đọc file audit cần quyền: SQL Server 2019 trở về trước cần CONTROL SERVER; từ SQL Server 2022, VIEW SERVER SECURITY AUDIT là đủ.

Lương là dữ liệu cá nhân

Từ 01/01/2026, Luật Bảo vệ dữ liệu cá nhân số 91/2025/QH15 có hiệu lực (Cổng thông tin Bộ Công an). Luật trao cho chủ thể dữ liệu các quyền như xem và yêu cầu chỉnh sửa dữ liệu cá nhân của mình (Điều 4). Kiểm soát ai được đọc lương và ghi vết việc đọc là các biện pháp kỹ thuật phục vụ hướng đó; bài này không trích dẫn điều khoản kỹ thuật cụ thể vì chưa đối chiếu được toàn văn hợp nhất.

Những chỗ hay hiểu sai

  • "Che cột là đủ để giấu lương." Che cột chỉ đổi kết quả hiển thị; WHERE vẫn lọc trên giá trị thật, nên người chạy được truy vấn ad hoc dò ra số thật trong khoảng 26 lần. Không cấp quyền đọc bảng chi tiết mới là ranh giới.
  • "Lọc bảng nhân viên là lương cũng được lọc theo." Predicate chỉ lọc bảng nó được gắn, không đi theo join. Policy chỉ trên NhanVien để quản lý cửa hàng 3 đọc 2.400 dòng BangLuong. Gắn predicate lên mọi bảng chứa dữ liệu cần lọc.
  • "RLS theo SESSION_CONTEXT chặn được người có quyền đọc." Nó tin ứng dụng đặt ngữ cảnh đúng. Người có kết nối SQL trực tiếp tự đặt ngữ cảnh của mình. RLS là kiểm soát tầng ứng dụng, không thay quyền tối thiểu.
  • "View tổng là giải quyết xong cho kế toán." Che cột đi theo ngữ cảnh người gọi, nên SUM trên cột đã che vẫn ra 0. Phải dùng module EXECUTE AS để gộp dưới ngữ cảnh có UNMASK.
  • "Audit SELECT trên bảng là truy được mọi người xem lương." Lệnh đọc bên trong thủ tục EXECUTE AS OWNER ghi dưới chủ sở hữu, không phải người gọi. Phải audit cả EXECUTE.

Đọc tiếp

Nguồn

Đọc tiếp