Thiết kế phân quyền (tính năng lập lịch)
Thiết kế phân quyền (tính năng lập lịch)
1. Tổng quan
Phân quyền = Quyền sử dụng + Khu vực giới hạn + Thao tác có thể thực hiện
1.1. Quyền sử dụng
Một account có thể được / không được cấp quyền sử dụng tính năng.
1.2. Khu vực giới hạn
Khu vực giới hạn bản chất là khu vực địa lý được phép tối ưu ở trong đó. Hiện tại trong XO thì
khu vực giới hạn này đang gọi là group. Một account có thể thực hiện các thao tác nhất định về
lập lịch trên khu vực giới hạn.
Mỗi khu vực giới hạn được định nghĩa bởi:
- Định danh (id): 1 mã duy nhất định danh khu vực địa lý
- Boundary của khu vực (boundary): là 1 polygon có nhiệm vụ giới hạn khu vực thực hiện tối
ưu (chỉ được phép định nghĩa
- Tên của khu vực (name): Ví dụ: Hà Nội - Hà Đông
- Loại khu vực (unit_type): Quận / huyện (DISTRICT), tỉnh / thành phố (PROVINCE), Khu
vực (AREA), Quốc gia (COUNTRY).
- Quan hệ cha / con giữa các khu vực: Ví dụ: Hà Đông là 1 quận thuộc Hà Nội thì với group Hà
Đông, lưu thêm 1 parent_id với giá trị là ID của thành phố Hà Nội.
1.3. Thao tác có thể thực hiện
Trên 1 khu vực giới hạn, có thể thực hiện được các thao tác sau
- Tạo (CREATE) yêu cầu tối ưu.
- Xem (VIEW) yêu cầu tối ưu và các lịch liên quan: Xem yêu cầu tối ưu thuộc khu vực.
- Sửa (EDIT) yêu cầu tối ưu và các lịch liên quan: Cập nhật thông tin (tên, thời gian, phụ thuộc),
hủy yêu cầu,...
● Lưu ý:
- Người dùng sẽ có thể thực hiện các thao tác tương tự đối với các yêu cầu, lịch liên
quan, chiến dịch và chu kỳ thuộc khu vực giới hạn.
- Người dùng sẽ có toàn quyền (VIEW, EDIT) đối với yêu cầu, các lịch liên quan, chiến
dịch và chu kỳ mà mình tạo.
- Đối với chiến dịch, các thao tác được thực hiện:
- Tạo (CREATE) chiến dịch tối ưu.
- Xem (VIEW) chiến dịch tối ưu và các chu kỳ của chiến dịch.
- Sửa (EDIT) chiến dịch tối ưu, và các chu kỳ của chiến dịch: sửa tên chiến dịch,
sửa lịch tác động đã đặt, chuyển trạng thái của chu kỳ, rollback chu kỳ, đóng chu
kỳ, đóng chiến dịch, xóa chiến dịch..
2. Thiết kế luồng hoạt động
2.1. Một số ví dụ
- Người dùng A có quyền VIEW trên khu vực quận Hà Đông -> có thể VIEW tất cả các
yêu cầu & lịch liên quan thuộc quận Hà Đông, nhưng không thể tạo yêu cầu mới trên
khu vực quận Hà Đông & chỉnh sửa bất kì yêu cầu nào thuộc quận Hà Đông.
Người dùng A cũng sẽ có quyền VIEW với tất cả các chiến dịch thuộc khu vực quận Hà
Đông
- Người dùng B có quyền CREATE trên khu vực quận Hoàn Kiếm, VIEW trên khu vực
quận Hai Bà Trưng. Người dùng B sẽ tạo được yêu cầu & lịch liên quan mới trên khu
vực quận Hoàn Kiếm, VIEW & EDIT yêu cầu mình tạo, nhưng không xem được các yêu
cầu khác trong quận. Người dùng B chỉ có quyền xem các yêu cầu & lịch liên quan
thuộc quận Hai Bà Trưng.
Người dùng B sẽ có quyền VIEW tất cả các chiến dịch thuộc khu vực quận Hai Bà
Trưng
2.2. Thiết kế CSDL
2.3. Luồng hoạt động
1. Người dùng truy cập vào tính năng Lập lịch
- Hệ thống cần kiểm tra quyền của người dùng đối với tính năng lập lịch
- Nếu người dùng được quyền truy cập, chuyển tới bước sau.
2. Người dùng thực hiện 1 thao tác (thêm / sửa / xóa / xem chi tiết) yêu cầu
- Hệ thống cần kiểm tra quyền của người dùng đối với khu vực mà yêu cầu thuộc vào:
● Truy vấn từ database: Kiểm tra xem người dùng có quyền CREATE / VIEW /
EDIT trên khu vực chứa yêu cầu không?
- Kiểm tra tương tự nếu người dùng thao tác trên 1 chiến dịch
2.4. Hướng triển khai
- Khi người dùng truy cập vào tính năng Lập lịch, cần thực hiện kiểm tra người dùng có
quyền truy cập vào tính năng lập lịch hay không bằng cách thêm mapping vào bảng
role_endpoint_mapping, sau đó hệ thống thực hiện đọc thông tin các API được
phép truy cập từ JWT như hiện tại.
- Cần thực hiện kiểm tra quyền của người dùng ở tầng Service, trước khi gọi method
thao tác. Có thể tham khảo việc tạo 1 module “mini” Spring Security ACL:
- Tạo 1 custom class implement lớp PermissionEvaluator của Spring. Tại đó
implement các logic gọi tới DB để kiểm tra quyền của người dùng, domain object
chung của hệ thống sẽ là khu vực giới hạn, nhưng khi kiểm tra quyền của thao
tác đối với yêu cầu hoặc chiến dịch thì cần tham chiếu tới khu vực giới hạn để
kiểm tra.
- Sử dụng annotation @PreAuthorize và ứng dụng SpEL để truyền target
domain object và permission vào để kiểm tra
- Nguồn tham khảo:
https://stackoverflow.com/questions/36696393/how-to-implement-custom-spring-security
-acl
- Để kiểm tra cây phân quyền 1 cách nhanh chóng, sử dụng Recursive CTE mà Postgres
và hầu hết các DB hiện đại đều đang hỗ trợ, ví dụ ta cần lấy tất cả những khu vực “cha”
của khu vực Hà Nội - Cầu Giấy, ta thực hiện query như sau:
with recursive leaves as (
select id
, name
, parent_id
from xo.test_group
where id = 'Ha Noi - Cau Giay'
union
-- recursively get all parents of current leaf
select g.id
, g.name
, g.parent_id
from xo.test_group g
inner join leaves p
on g.id = p.parent_id
)
select *
from leaves;
- Do các thao tác gọi tới DB để kiểm tra quyền sẽ gây ảnh hưởng tới performance hệ
thống, cần có cơ chế caching để giảm số lần gọi tới DB.
- Tham khảo Caffeine cache của Google: https://github.com/ben-manes/caffeine
- Khi caching quyền thao tác của 1 user, cần thực hiện:
- Tự động expire theo kích thước dữ liệu cache
- Tự động expire sau 1 khoảng thời gian (TTL)
- Cần có cơ chế đồng nhất dữ liệu cache ở nhiều instance khác nhau để đảm bảo
tính nhất quán -> Triển khai cơ chế cache sync sử dụng Kafka.
- Khi có thao tác chỉnh sửa quyền -> gửi message đến Kafka. Tất cả các
backend instances khi nhận được message cần refresh dữ liệu phần cập
nhật ở trong cache.
- Cài đặt cơ chế Cache Refresh: Để phòng trường hợp có lỗi xảy ra khi
gửi / nhận message từ Kafka dẫn đến cache không được reload, cần set
thời gian expire cho cache (theo kích thước dữ liệu / TTL)
- Lưu ý: tham khảo mục 3.3 để có thông tin 1 số lỗi có thể xảy ra khi cài
đặt cache sync, từ đó coding cho phù hợp.
3. Một số vấn đề liên quan
3.1. Tính kế thừa phân quyền
- Một account có quyền X trên khu vực KV1 sẽ có quyền X trên tất cả các tỉnh, tất cả các
huyện thuộc KV1.
3.2. Quyền trên chiến dịch
- Hiện tại: người nào tạo chiến dịch thì người đó mới có quyền thay đổi chiến dịch đó (Ví dụ:
Đóng chiến dịch, đóng chu kỳ).
- Mong đợi: Áp dụng tất cả các quyền trên khu vực của chiến dịch vào chiến dịch đó.
Ví dụ: Người A có quyền VIEW, EDIT trên khu vực X, khi đó A sẽ có quyền VIEW và EDIT trên
tất cả các chiến dịch nằm trong khu vực X
- Phân quyền cho các bot. Các bot có vai trò vận hành chiến dịch cũng nên được phân quyền.
3.3. Cache Sync Failure
Bảng dưới đây liệt kê các trường hợp luồng đồng bộ dữ liệu phân quyền giữa cache và DB có
thể bị thất bại, ảnh hưởng và 1 số giải pháp đề xuất.
Failure Scenario Mô tả Impact Solution
Logging: trước hết cần logging error
trong trường hợp này. Logging đủ
thông tin để phục vụ debugging sau
này.
Transaction: đảm bảo thao tác update
có tính transactional, khi xảy ra failure
thì DB rollback lại trạng thái trước
update.
Retry: Retry theo cơ chế exponential
Thao tác update DB backoff trong ngưỡng thời gian cho
không thực hiện thành phép.
Thực hiện thao tác công. Không ảnh hưởng Error Response: Báo lỗi 500 về phía
Database Update update DB không tới trạng thái của cache client.
Failure thành công. tại phía backend. Alerting: Đẩy cảnh báo cho đội dev.
Cache Inconsistency:
Cache gặp lỗi bị ảnh
Backend instance hưởng, dữ liệu không
không thể cập nhật đồng nhất với trong DB.
internal cache Những lần tiếp theo vẫn
thành công. Xác bị thì khả năng có lỗi Logging: Logging warn trong trường
suất xảy ra hiện nghiêm trọng (do hiện hợp update thất bại -> phục vụ
Local Cache tượng này tương tượng này xảy ra với debugging sau này.
Update Failure đối hiếm. xác suất cực kỳ thấp). Alerting: Đẩy cảnh báo cho đội dev.
Cache Inconsítency:
Producer (bên gửi) Không có thông tin cập
publish bản tin lên nhật, dẫn tới cache tại
message queue để backend instances đều Retry: Retry publishing (có cơ chế
thông báo cập không được cập nhật, backoff phù hợp).
Message nhật. Thao tác kết quả dữ liệu tại Logging: Logging tại phía backend để
Publishing publish này thất cache không consistent phục vụ debugging sau này.
Failure bại. với dữ liệu trong DB. Alerting: Đẩy cảnh báo cho đội dev.
Message queue Reliable Message Queue: Sử dụng
(bên truyền bản Cache Inconsítency: Kafka để đảm bảo không mất bản tin.
tin) nhận bản tin từ Mất thông tin, dẫn tới Bản tin sau khi nhận tại Kafka sẽ được
producer, tuy nhiên cache tại các backend lưu trữ trong 1 khoảng thời gian nhất
Message thất bại trong việc instances đều không định. Khi gặp vấn đề có thể consume
Delivery Failure truyền bản tin này được cập nhật. lại.
tới consumer. Periodic Full Cache Refresh: Định kỳ
(hàng giờ, hàng ngày) thực hiện refresh
toàn bộ cache tại backend instance.
Monitoring: Monitor Kafka broker, topic
Kafka.
Cache Inconsítency:
Mất thông tin, dẫn tới
Message Consumer (bên cache tại backend Logging: Logging tại phía backend để
Processing nhận) xử lý bản tin instance bị lỗi không phục vụ debugging sau này.
Failure thất bại. được cập nhật. Alerting: Đẩy cảnh báo cho đội dev.