Casbin
Kho lưu trữ chính thức: casbin/casbin: An authorization library that supports access control models like ACL, RBAC, ABAC in Golang (github.com)
Tài liệu chính thức: Tổng quan | Casbin
TIP
Bài viết này chỉ là một bài viết nhập môn về Casbin, nếu muốn tìm hiểu chi tiết hơn hãy truy cập trang web chính thức để học tập.
Giới thiệu
Trong một hệ thống, lập trình viên backend cần chịu trách nhiệm quản lý quyền API, điều này đòi hỏi rất nhiều công việc. Nếu mỗi dự án đều phải tự viết tay một hệ thống thì sẽ lãng phí rất nhiều thời gian. Các công ty lớn với nhiều nhân lực và vật lực hơn sẽ có xu hướng tự phát triển một framework quyền hạn riêng, nhưng hầu hết các công ty vừa và nhỏ không thể chịu được chi phí phát triển này, vì vậy các framework quyền hạn mã nguồn mở trên thị trường trở thành lựa chọn hàng đầu của họ. Casbin chính là một thư viện kiểm soát truy cập mã nguồn mở và hiệu quả như vậy, bản thân nó được phát triển bằng ngôn ngữ Go, đồng thời cũng hỗ trợ các ngôn ngữ phổ biến khác.
Cần lưu ý rằng, Casbin chỉ là một framework kiểm soát truy cập, chỉ chịu trách nhiệm kiểm soát truy cập, logic xác thực truy cập không do Casbin chịu trách nhiệm, nó chỉ lưu trữ mối quan hệ ánh xạ giữa người dùng và vai trò. Hỗ trợ các mô hình kiểm soát truy cập sau:
- ACL (Access Control List, Danh sách kiểm soát truy cập)
- ACL với superuser
- ACL không có người dùng: Đặc biệt hữu ích cho các hệ thống không có xác thực hoặc đăng nhập người dùng.
- ACL không có tài nguyên: Một số tình huống có thể chỉ nhắm vào loại tài nguyên, thay vì từng tài nguyên riêng lẻ, chẳng hạn như các quyền
write-article,read-log. Nó không kiểm soát quyền truy cập vào một bài viết hoặc nhật ký cụ thể. - RBAC (Kiểm soát truy cập dựa trên vai trò)
- RBAC hỗ trợ vai trò tài nguyên: Người dùng và tài nguyên có thể đồng thời có vai trò (hoặc nhóm).
- RBAC hỗ trợ domain/tenant: Người dùng có thể thiết lập các bộ vai trò khác nhau cho các domain/tenant khác nhau.
- ABAC (Kiểm soát truy cập dựa trên thuộc tính): Hỗ trợ sử dụng cú pháp như
resource.Ownerđể lấy thuộc tính của phần tử. - RESTful: Hỗ trợ đường dẫn, như
/res/*,/res/:idvà các phương thức HTTP, nhưGET,POST,PUT,DELETE. - Deny ưu tiên: Hỗ trợ ủy quyền cho phép và từ chối, từ chối được ưu tiên hơn cho phép.
- Độ ưu tiên: Các quy tắc chính sách xác định độ ưu tiên theo thứ tự trước sau, tương tự như quy tắc tường lửa.
Nguyên lý hoạt động
Trong Casbin, mô hình kiểm soát truy cập được trừu tượng hóa thành file cấu hình dựa trên PERM, PERM chỉ Policy (Chính sách), Effect (Hiệu ứng), Request (Yêu cầu), Matcher (Bộ so khớp). Khi sửa đổi cơ chế ủy quyền trong dự án, chỉ cần sửa đổi đơn giản file cấu hình là được. Nội dung file Model bình thường như sau:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.actĐây là mô hình kiểm soát truy cập ACL đơn giản nhất.
Chính sách
Trong file cấu hình, phần định nghĩa chính sách là
[policy_definition]
p = sub, obj, actp tức là policy, không thể thay thế bằng ký tự khác, sub chỉ subject, là chủ thể chính sách, obj tức object, là đối tượng chính sách, act tức action, chỉ hành vi.
p = sub, obj, actCũng có thể có trường thứ tư eft, nếu bỏ qua thì eft mặc định là allow.
p=sub, obj, act, eftDòng định nghĩa này chỉ mô tả cách viết policy, không phải định nghĩa chính sách cụ thể. Dưới đây là một ví dụ policy cụ thể
p, jojo, cake, eatp đại diện cho đây là một định nghĩa quy tắc chính sách, jojo là chủ thể chính sách, cake là đối tượng chính sách, eat là hành vi, ý nghĩa hoàn chỉnh là chủ thể jojo có thể thực hiện hành vi eat đối với đối tượng cake. Các quy tắc chính sách cụ thể sẽ không xuất hiện trong file model, sẽ có file policy chuyên dụng hoặc cơ sở dữ liệu để lưu trữ chính sách.
Yêu cầu
Trong file cấu hình, phần định nghĩa yêu cầu là
[request_definition]
r = sub, obj, actr tức là request, không thể thay thế bằng ký tự khác, sub tức subject, chỉ chủ thể yêu cầu, obj tức object, chỉ đối tượng yêu cầu, act tức action, chỉ hành vi yêu cầu. Thông thường tên trường định nghĩa yêu cầu và định nghĩa chính sách đều giống nhau. Phần yêu cầu không do casbin chịu trách nhiệm, điều này do nhà phát triển tự quyết định cái gì là chủ thể yêu cầu, cái gì là đối tượng yêu cầu, casbin chỉ cần chịu trách nhiệm kiểm soát truy cập dựa trên các trường được truyền vào.
So khớp
Trong file cấu hình, phần định nghĩa so khớp là
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.actm chỉ matcher, không thể thay thế bằng ký tự khác, sau đó là quy tắc so khớp tương ứng, trên đây là một biểu thức boolean đơn giản, ý nghĩa là tất cả các trường của yêu cầu được truyền vào đều giống với các trường của quy tắc chính sách thì so khớp, tất nhiên nó cũng có thể là ký tự đại diện hoặc biểu thức chính quy có khả năng biểu đạt mạnh hơn.
Ngoài ra, matcher còn hỗ trợ cú pháp in, ví dụ
[matchers]
m = r.sub in ("root","admin")Cũng có thể là
[matchers]
m = r.sub.Name in (r.obj.Admins)e.Enforce(Sub{Name: "alice"}, Obj{Name: "a book", Admins: []interface{}{"alice", "bob"}})Khi thực hiện so khớp, Casbin không thực hiện kiểm tra kiểu, mà coi nó là interface và kiểm tra == xem có bằng nhau không.
Hiệu ứng
Phần định nghĩa hiệu ứng thực hiện phán đoán logic tổ hợp lại đối với kết quả so khớp. Trong file cấu hình, phần định nghĩa hiệu ứng là
[policy_effect]
e = some(where (p.eft == allow))e tức là effect, không thể thay thế bằng ký tự khác. Lượng từ some phán đoán xem có tồn tại một quy tắc chính sách thỏa mãn bộ so khớp không. Lượng từ any thì phán đoán xem tất cả các quy tắc chính sách có thỏa mãn bộ so khớp không.
some(where (p.eft == allow))Quy tắc này có ý nghĩa là trong kết quả so khớp có một kết quả allow, thì kết quả cuối cùng là allow.
e = !some(where (p.eft == deny))Quy tắc này có ý nghĩa là trong kết quả so khớp chỉ cần không tồn tại kết quả deny, thì kết quả cuối cùng là allow.
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))Quy tắc này có ý nghĩa là trong kết quả so khớp, có một cái là allow, và không tồn tại kết quả deny, thì kết quả cuối cùng là allow.
Mặc dù Casbin đã thiết kế cú pháp cho các hiệu ứng chính sách nêu trên, nhưng việc thực thi hiện tại chỉ sử dụng các hiệu ứng chính sách được mã hóa cứng. Họ cho rằng tính linh hoạt này không cần thiết lắm. Cho đến nay bạn phải sử dụng các policy effects được tích hợp sẵn, không thể tùy chỉnh, các policy effects được hỗ trợ tích hợp sẵn như sau.
| Định nghĩa Policy effect | Ý nghĩa | Ví dụ |
|---|---|---|
| some(where (p.eft == allow)) | allow-override | ACL, RBAC, etc. |
| !some(where (p.eft == deny)) | deny-override | Từ chối ghi đè |
| some(where (p.eft == allow)) && !some(where (p.eft == deny)) | allow-and-deny | Đồng ý và từ chối |
| priority(p.eft) || deny | priority | Độ ưu tiên |
| subjectPriority(p.eft) | Ưu tiên dựa trên vai trò | Ưu tiên chủ thể |
TIP
Bốn định nghĩa trên đều có thể định nghĩa nhiều cái, cú pháp là
type+number, ví dụr2,p2,e2,m2.File model có thể có chú thích, dùng ký hiệu
#để chú thích.
Ví dụ
Dưới đây là một ví dụ, minh họa quá trình làm việc của file model. Đầu tiên định nghĩa một file model ACL đơn giản như sau
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.actFile Policy như sau
p, alice, data1, read
p, bob, data2, writeQuá trình trừu tượng hóa chủ thể, đối tượng, hành vi do logic nghiệp vụ quyết định, ở đây không quan trọng nên bỏ qua. Dưới đây là cách hiển thị yêu cầu được truyền vào theo cách đơn giản nhất, như sau
alice, data1, read
bob, data1, read
alice, data2, write
bob, data2, writeFile policy định nghĩa alice có quyền thực hiện thao tác read đối với data1, bob có quyền thực hiện thao tác write đối với data2, vậy trong các yêu cầu được truyền vào
alice, data1, readbiểu thị alice muốn thực hiện thao tác read đối với data1,
bob, data1, readbiểu thị bob muốn thực hiện thao tác read đối với data1, các trường hợp còn lại tương tự. Vậy kết quả cuối cùng là
true
false
false
trueĐây là một ví dụ ACL đơn giản nhất, trang web chính thức của Casbin có thể thực hiện chỉnh sửa và kiểm tra ví dụ trực tuyến, truy cập Casbin editor để kiểm tra.
RBAC
RBAC (Role-Based-Access-Controll), Kiểm soát truy cập dựa trên vai trò, so với mô hình ACL sẽ có thêm một [role definition], dưới đây là một mô hình RBAC đơn giản
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.actTrong đó định nghĩa vai trò như sau
[role_definition]
g = _, _g chỉ group, không thể thay thế bằng ký tự khác, hỗ trợ tạo nhiều cái bằng cách type+number, _ là ký hiệu chỗ, biểu thị có bao nhiêu tham số đầu vào. Nhìn chung, trong Policy, g thường có định dạng như sau
g, alice, data2_admin
g, mike, data1_admin
g, data1_admin data2_adminalice chỉ chủ thể, data2_admin chỉ vai trò, nói một cách nghiêm ngặt thì casbin đều coi chúng là chuỗi, cách hiểu ý nghĩa và sử dụng như thế nào phụ thuộc vào nhà phát triển.
g, alice, data2_adminbiểu thị alice có vai trò data2_admin
g, mike, data1_adminbiểu thị mike có vai trò data1_admin
g, data1_admin data2_adminbiểu thị vai trò data1_admin có vai trò data2_admin, đây là mối quan hệ kế thừa giữa các vai trò.
Mô hình vai trò tài nguyên
Mô hình vai trò tài nguyên thêm một g2, làm định nghĩa vai trò của tài nguyên, định nghĩa model như sau
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.actVí dụ định nghĩa Policy như sau
p, alice, data1, read
p, bob, data2, write
p, data_group_admin, data_group, write
g, alice, data_group_admin
g2, data1, data_group
g2, data2, data_groupTrong đó g2 định nghĩa nhóm vai trò tài nguyên, gán tài nguyên cho các vai trò khác nhau, đồng thời quy định mối quan hệ người dùng giữa vai trò người dùng và vai trò tài nguyên.
p, data_group_admin, data_group, writeChính sách này định nghĩa người dùng có vai trò data_group_admin có thể thực hiện thao tác write đối với tài nguyên có vai trò data_group.
Mô hình domain đa tenant
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.actMô hình domain đa tenant so với mô hình RBAC truyền thống có thêm trường dom, dùng để biểu thị lĩnh vực mà chủ thể thuộc về. Ví dụ Policy như sau
p, admin, domain1, data1, read
p, admin, domain1, data1, write
p, admin, domain2, data2, read
p, admin, domain2, data2, write
g, alice, admin, domain1
g, bob, admin, domain2Ví dụ
p, admin, domain1, data1, readđịnh nghĩa chủ thể admin thuộc lĩnh vực domain1 có quyền thực hiện thao tác read đối với data1
g, alice, admin, domain1định nghĩa alice thuộc domain1 có vai trò admin
ABAC
Mô hình ABAC (Attribute-Based Access Control) là mô hình kiểm soát truy cập dựa trên thuộc tính. Trong mô hình này, quyền truy cập được xác định dựa trên các thuộc tính của chủ thể, tài nguyên, hành động và môi trường.
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == r.obj.OwnerTrong ví dụ trên, matcher sử dụng thuộc tính Owner của đối tượng để so khớp với chủ thể yêu cầu.
Ví dụ ABAC
type Subject struct {
Name string
Age int
}
type Object struct {
Owner string
Data string
}
e := casbin.NewEnforcer("model.conf")
sub := Subject{Name: "alice", Age: 30}
obj := Object{Owner: "alice", Data: "secret"}
result, _ := e.Enforce(sub, obj, "read")Trong ví dụ này, chỉ khi Owner của đối tượng khớp với tên của chủ thể thì mới cho phép truy cập.
Sử dụng Casbin trong Go
Cài đặt
go get github.com/casbin/casbin/v2Khởi tạo Enforcer
package main
import (
"github.com/casbin/casbin/v2"
)
func main() {
e, _ := casbin.NewEnforcer("model.conf", "policy.csv")
// Kiểm tra quyền
sub := "alice"
obj := "data1"
act := "read"
allowed, _ := e.Enforce(sub, obj, act)
if allowed {
println("Được phép truy cập")
} else {
println("Bị từ chối truy cập")
}
}Model file (model.conf)
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.actPolicy file (policy.csv)
p, alice, data1, read
p, bob, data2, writeLưu trữ Policy
Casbin hỗ trợ nhiều loại lưu trữ policy khác nhau:
- File CSV: Đơn giản, phù hợp cho dự án nhỏ
- Database: MySQL, PostgreSQL, SQLite
- Adapter: Hỗ trợ các adapter khác nhau để kết nối với các hệ thống lưu trữ
Sử dụng Database Adapter
import (
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
)
func main() {
adapter, _ := gormadapter.NewGormAdapter("mysql", "user:password@/dbname")
e, _ := casbin.NewEnforcer("model.conf", adapter)
}Best Practices
- Tách biệt Model và Policy: Giữ file model và policy riêng biệt để dễ quản lý
- Sử dụng RBAC cho hệ thống phức tạp: RBAC phù hợp cho các hệ thống có nhiều vai trò và quyền hạn
- Kiểm tra hiệu suất: Sử dụng cache cho các kiểm tra quyền thường xuyên
- Audit logging: Ghi log các quyết định truy cập để audit
- Test kỹ lưỡng: Kiểm tra các kịch bản truy cập khác nhau
Kết luận
Casbin là một thư viện mạnh mẽ và linh hoạt cho việc kiểm soát truy cập trong ứng dụng Go. Với sự hỗ trợ cho nhiều mô hình kiểm soát truy cập khác nhau và khả năng tích hợp với nhiều hệ thống lưu trữ, Casbin là lựa chọn tuyệt vời cho cả dự án nhỏ và lớn.
