Mô hình RBAC & Phân quyền
Quyết định
Phần tiêu đề “Quyết định”RBAC dựa trên mã quyền với định nghĩa vai trò theo từng tenant.
Nền tảng định nghĩa một danh mục cố định các mã quyền (ví dụ: accounting.journalentry.create). Quản trị viên tenant định nghĩa vai trò và gán mã quyền cho chúng. Người dùng được gán một hoặc nhiều vai trò trong tenant của họ. JWT được cấp khi đăng nhập mang các mã quyền đã được tổng hợp của người dùng. Phân quyền dựa trên policy của ASP.NET Core thực thi các mã này ở tầng controller/endpoint. SQL Server RLS (xem multitenancy.md) thực thi cô lập tenant độc lập ở tầng dữ liệu.
Bối cảnh
Phần tiêu đề “Bối cảnh”Mọi module trong hệ thống — Kế toán, Kho hàng, Mua hàng, Bán hàng — đều yêu cầu kiểm soát truy cập. Mô hình truy cập phải:
- Được thực thi ở tầng API, không chỉ ở UI
- Hỗ trợ tùy chỉnh theo tenant (một “Quản lý” ở Tenant A có thể có quyền khác với “Quản lý” ở Tenant B)
- Phân biệt giữa thao tác cấp nền tảng (cấp phát tenant, cấu hình hệ thống) và thao tác cấp tenant
- Có khả năng kiểm toán (ai làm gì, theo quyền nào)
- Dễ hiểu để đội ngũ ≤5 kỹ sư duy trì
Các chủ thể
Phần tiêu đề “Các chủ thể”| Chủ thể | Mô tả |
|---|---|
| Quản trị viên Hệ thống | Người vận hành nền tảng. Quản lý tenant, thanh toán, cấu hình nền tảng. Không thuộc phạm vi bất kỳ tenant nào. Tách biệt với người dùng tenant. |
| Quản trị viên Tenant | Người dùng quyền cao trong một tenant. Quản lý người dùng, vai trò, cấu hình tenant. Được gán vai trò tích hợp sẵn TenantAdministrator. |
| Người dùng Tenant | Bất kỳ người dùng nào hoạt động trong một tenant. Có một hoặc nhiều vai trò do tenant định nghĩa. |
Quản trị viên Hệ thống xác thực riêng biệt và sử dụng PlatformAdminDbContext (xem multitenancy.md). Họ không thể truy cập dữ liệu tenant qua các API ứng dụng thông thường.
Danh mục mã quyền
Phần tiêu đề “Danh mục mã quyền”Mã quyền theo quy ước đặt tên: {module}.{resource}.{action}
Module: core, accounting, inventory, procurement, sales, reporting
Hành động: view, create, edit, delete, approve, void, export, manage
Mã quyền Phase 1
Phần tiêu đề “Mã quyền Phase 1”| Mã | Mô tả |
|---|---|
core.users.view | Xem danh sách và hồ sơ người dùng |
core.users.manage | Tạo, sửa, vô hiệu hóa người dùng |
core.roles.view | Xem định nghĩa vai trò |
core.roles.manage | Tạo và sửa vai trò, gán mã quyền |
core.tenantconfig.view | Xem cấu hình tenant |
core.tenantconfig.manage | Sửa cấu hình tenant, cài đặt hóa đơn điện tử |
core.auditlog.view | Xem nhật ký kiểm toán |
Kế toán
Phần tiêu đề “Kế toán”| Mã | Mô tả |
|---|---|
accounting.journalentry.view | Xem bút toán và sổ cái tổng hợp |
accounting.journalentry.create | Tạo bút toán thủ công |
accounting.journalentry.void | Hủy bút toán đã ghi sổ (tạo bút toán đảo) |
accounting.period.view | Xem các kỳ tài chính |
accounting.period.manage | Mở và đóng kỳ tài chính |
accounting.chartofaccounts.view | Xem hệ thống tài khoản |
accounting.chartofaccounts.manage | Thêm và sửa tài khoản |
accounting.ap.view | Xem hóa đơn nhà cung cấp và thanh toán |
accounting.ap.create | Ghi nhận hóa đơn nhà cung cấp và thanh toán |
accounting.ap.approve | Duyệt hóa đơn nhà cung cấp để thanh toán |
accounting.ar.view | Xem hóa đơn khách hàng và biên lai |
accounting.ar.create | Xuất hóa đơn khách hàng và ghi nhận biên lai |
accounting.ar.void | Hủy hóa đơn khách hàng |
accounting.einvoice.issue | Gửi hóa đơn đến nhà cung cấp hóa đơn điện tử |
accounting.einvoice.cancel | Hủy hóa đơn điện tử đã gửi |
accounting.tax.view | Xem tờ khai VAT, TNDN, TNCN |
accounting.tax.manage | Lập và nộp tờ khai thuế |
accounting.reports.view | Xem báo cáo tài chính (IFRS và VAS) |
accounting.reports.export | Xuất báo cáo tài chính ra PDF/Excel |
Kho hàng
Phần tiêu đề “Kho hàng”| Mã | Mô tả |
|---|---|
inventory.products.view | Xem danh mục sản phẩm |
inventory.products.manage | Tạo và sửa sản phẩm |
inventory.stock.view | Xem mức tồn kho và biến động |
inventory.stockadjustment.create | Tạo phiếu điều chỉnh tồn kho |
inventory.stockadjustment.approve | Duyệt điều chỉnh tồn kho vượt ngưỡng |
inventory.stocktake.manage | Khởi tạo và hoàn tất kiểm kê |
Mua hàng
Phần tiêu đề “Mua hàng”| Mã | Mô tả |
|---|---|
procurement.suppliers.view | Xem danh sách nhà cung cấp |
procurement.suppliers.manage | Tạo và sửa nhà cung cấp |
procurement.purchaserequisition.create | Lập phiếu yêu cầu mua hàng |
procurement.purchaserequisition.approve | Duyệt phiếu yêu cầu mua hàng |
procurement.purchaseorder.create | Tạo đơn đặt mua hàng |
procurement.purchaseorder.approve | Duyệt đơn đặt mua hàng |
procurement.goodsreceipt.create | Ghi nhận nhập kho |
procurement.goodsreceipt.approve | Duyệt phiếu nhập kho |
Bán hàng
Phần tiêu đề “Bán hàng”| Mã | Mô tả |
|---|---|
sales.customers.view | Xem danh sách khách hàng |
sales.customers.manage | Tạo và sửa khách hàng |
sales.quotation.create | Tạo báo giá |
sales.salesorder.create | Tạo đơn bán hàng |
sales.salesorder.approve | Duyệt đơn bán hàng |
sales.deliverynote.create | Tạo phiếu giao hàng |
sales.deliverynote.confirm | Xác nhận giao hàng (kích hoạt xuất hóa đơn AR) |
Báo cáo
Phần tiêu đề “Báo cáo”| Mã | Mô tả |
|---|---|
reporting.dashboards.view | Xem dashboard có thể cấu hình |
reporting.statutory.view | Xem báo cáo pháp định VAS |
reporting.statutory.export | Xuất báo cáo pháp định VAS |
Các vai trò hệ thống tích hợp sẵn
Phần tiêu đề “Các vai trò hệ thống tích hợp sẵn”Các vai trò này được seed cho mọi tenant mới. Quản trị viên tenant không thể xóa chúng nhưng có thể đổi tên và điều chỉnh mã quyền (ngoại trừ TenantAdministrator).
| Vai trò | Quyền mặc định | Ghi chú |
|---|---|---|
TenantAdministrator | Tất cả mã | Không thể sửa hoặc xóa |
Accountant | Tất cả mã accounting.* + reporting.* | Có thể sửa bởi quản trị viên tenant |
Purchasing | Tất cả mã procurement.* + inventory.stock.view | Có thể sửa |
Sales | Tất cả mã sales.* + inventory.stock.view | Có thể sửa |
Viewer | Tất cả mã *.view và *.export | Chỉ đọc toàn hệ thống; có thể sửa |
Quản trị viên tenant có thể tạo thêm vai trò với bất kỳ tập con nào của danh mục.
Mô hình dữ liệu
Phần tiêu đề “Mô hình dữ liệu”Bảng cấp nền tảng (không áp dụng RLS)
Phần tiêu đề “Bảng cấp nền tảng (không áp dụng RLS)”PermissionCodes (seed khi triển khai; không thể sửa qua UI)────────────────────────────────────────────────────────Code NVARCHAR(100) PK -- ví dụ: 'accounting.journalentry.create'Module NVARCHAR(50) NOT NULLResource NVARCHAR(50) NOT NULLAction NVARCHAR(50) NOT NULLDisplayName NVARCHAR(200) NOT NULLDescription NVARCHAR(500) NULLBảng có phạm vi tenant (áp dụng RLS)
Phần tiêu đề “Bảng có phạm vi tenant (áp dụng RLS)”TenantRoles────────────────────────────────────────────────────────RoleId UNIQUEIDENTIFIER PKTenantId UNIQUEIDENTIFIER NOT NULL (RLS)RoleName NVARCHAR(100) NOT NULLDescription NVARCHAR(500) NULLIsSystem BIT NOT NULL -- TRUE = seed, không thể xóaUNIQUE (TenantId, RoleName)TenantRolePermissions────────────────────────────────────────────────────────RoleId UNIQUEIDENTIFIER NOT NULL FK → TenantRolesPermissionCode NVARCHAR(100) NOT NULL FK → PermissionCodesPRIMARY KEY (RoleId, PermissionCode)TenantUsers────────────────────────────────────────────────────────TenantUserId UNIQUEIDENTIFIER PKTenantId UNIQUEIDENTIFIER NOT NULL (RLS)UserId UNIQUEIDENTIFIER NOT NULL FK → AspNetUsersDisplayName NVARCHAR(200) NOT NULLIsActive BIT NOT NULLLastLoginAt DATETIMEOFFSET NULLUNIQUE (TenantId, UserId)TenantUserRoles────────────────────────────────────────────────────────TenantUserId UNIQUEIDENTIFIER NOT NULL FK → TenantUsersRoleId UNIQUEIDENTIFIER NOT NULL FK → TenantRolesPRIMARY KEY (TenantUserId, RoleId)AspNetUsers là bảng ASP.NET Core Identity tiêu chuẩn (cấp nền tảng, không áp dụng RLS). Một người dùng có thể thuộc nhiều tenant qua các hàng TenantUsers riêng biệt.
Claims xác thực
Phần tiêu đề “Claims xác thực”Tập claims giống nhau bất kể phương thức vận chuyển xác thực:
{ "sub": "user-guid", "tenant_id": "tenant-guid", "tenant_user_id": "tenant-user-guid", "permissions": [ "accounting.journalentry.view", "accounting.journalentry.create", "accounting.ap.view", "accounting.ap.create", "reporting.dashboards.view" ]}Tại sao dùng quyền dưới dạng claims thay vì vai trò:
- Cho phép middleware phân quyền đưa ra quyết định mà không cần truy vấn cơ sở dữ liệu ở mỗi request.
- Tên vai trò do tenant định nghĩa và có thể trùng giữa các tenant. Mã quyền ổn định và được định nghĩa bởi nền tảng.
- Danh sách quyền của người dùng thông thường nhỏ (10–30 mã); kích thước claim không phải vấn đề trong thực tế.
Vận chuyển: Cookie (mặc định) vs. JWT
Phần tiêu đề “Vận chuyển: Cookie (mặc định) vs. JWT”Frontend React được host cùng với API trên cùng origin. Cookie auth của ASP.NET Core Identity là mặc định. Claims được lưu trong cookie HttpOnly, Secure, SameSite=Strict được mã hóa do ASP.NET Core Data Protection quản lý. Không cần lưu token trong trình duyệt.
Endpoint Bearer JWT (/auth/token) có sẵn cho các client không thể dùng cookie: ứng dụng mobile native Phase 3 và các tích hợp API bên thứ ba. Phân quyền dựa trên policy của ASP.NET Core thực thi cùng mã quyền bất kể scheme xác thực nào.
Thực thi
Phần tiêu đề “Thực thi”Tầng API
Phần tiêu đề “Tầng API”Phân quyền dựa trên policy của ASP.NET Core. Mỗi policy tương ứng với một mã quyền:
// Program.cs — đăng ký khi khởi động từ danh mục PermissionCodesbuilder.Services.AddAuthorization(options =>{ foreach (var code in permissionCatalog) options.AddPolicy(code, p => p.RequireClaim("permissions", code));});
// Controller[Authorize(Policy = "accounting.journalentry.create")][HttpPost("journal-entries")]public async Task<IActionResult> CreateJournalEntry(...) { ... }Tầng dữ liệu
Phần tiêu đề “Tầng dữ liệu”SQL Server RLS thực thi cô lập tenant độc lập. Người dùng có accounting.journalentry.view chỉ thấy các bút toán thuộc tenant của họ — RLS đảm bảo điều này bất kể mã ứng dụng có lọc đúng hay không.
Middleware
Phần tiêu đề “Middleware”Pipeline request phải:
- Xác thực chữ ký và thời hạn JWT
- Set
SESSION_CONTEXT(N'TenantId')trên kết nối cơ sở dữ liệu từ claimtenant_id - Điền vào service
ITenantContextvớiTenantIdvàTenantUserIdđể dùng trong domain service
Các bước này xảy ra trong auth/tenant middleware trước khi bất kỳ controller nào thực thi. Không controller hay domain service nào lấy TenantId từ dữ liệu người dùng nhập vào.
Tích hợp kiểm toán
Phần tiêu đề “Tích hợp kiểm toán”Mọi hành động được bảo vệ bởi quyền mà làm thay đổi dữ liệu phải được ghi vào nhật ký kiểm toán. Nhật ký ghi lại TenantUserId, hành động đã thực hiện, tài nguyên bị ảnh hưởng, và PermissionCode đã được sử dụng. Xem core-platform.md để biết schema nhật ký kiểm toán.
Vấn đề còn mở
Phần tiêu đề “Vấn đề còn mở”-
Quyền cấp trường (field-level) — Một số client có thể yêu cầu ẩn các trường cụ thể (ví dụ: giá vốn với nhân viên bán hàng). Quyền cấp trường được hoãn sang đánh giá customization engine Phase 2. Mã Phase 1 không được giả định cứng về việc ẩn trường.
-
Ủy quyền phê duyệt — Một số quy trình yêu cầu người dùng ủy quyền phê duyệt tạm thời (ví dụ: khi nghỉ phép). Cơ chế cho việc này chưa được thiết kế; sẽ được giải quyết khi thiết kế workflow engine (Phase 2).
-
Người dùng đa tenant — Một tư vấn hoặc kế toán có thể cần truy cập nhiều tenant. Mô hình dữ liệu hỗ trợ điều này (một
UserId→ nhiều hàngTenantUsers), nhưng UI/UX để chuyển đổi context tenant cần được thiết kế.