Skip to content

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:

  1. ACL (Access Control List, Danh sách kiểm soát truy cập)
  2. ACL với superuser
  3. 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.
  4. 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ể.
  5. RBAC (Kiểm soát truy cập dựa trên vai trò)
  6. 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).
  7. 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.
  8. 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ử.
  9. RESTful: Hỗ trợ đường dẫn, như /res/*, /res/:id và các phương thức HTTP, như GET, POST, PUT, DELETE.
  10. 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.
  11. Độ ư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:

bash
[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, act

p 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, act

Cũng có thể có trường thứ tư eft, nếu bỏ qua thì eft mặc định là allow.

p=sub, obj, act, eft

Dò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, eat

p đạ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, act

r 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.act

m 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)
go
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ĩaVí dụ
some(where (p.eft == allow))allow-overrideACL, RBAC, etc.
!some(where (p.eft == deny))deny-overrideTừ chối ghi đè
some(where (p.eft == allow)) && !some(where (p.eft == deny))allow-and-denyĐồng ý và từ chối
priority(p.eft) || denypriorityĐộ ưu tiên
subjectPriority(p.eft)Ưu tiên dựa trên vai tròƯu tiên chủ thể

TIP

  1. 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.

  2. 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.act

File Policy như sau

p, alice, data1, read
p, bob, data2, write

Quá 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, write

File 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, read

biểu thị alice muốn thực hiện thao tác read đối với data1,

bob, data1, read

biể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.act

Trong đó đị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_admin

alice 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_admin

biểu thị alice có vai trò data2_admin

g, mike, data1_admin

biểu thị mike có vai trò data1_admin

g, data1_admin data2_admin

biể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.act

Ví 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_group

Trong đó 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, write

Chí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.act

Mô 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, domain2

Ví 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.Owner

Trong 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

go
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

bash
go get github.com/casbin/casbin/v2

Khởi tạo Enforcer

go
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)

ini
[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

Policy file (policy.csv)

csv
p, alice, data1, read
p, bob, data2, write

Lưu trữ Policy

Casbin hỗ trợ nhiều loại lưu trữ policy khác nhau:

  1. File CSV: Đơn giản, phù hợp cho dự án nhỏ
  2. Database: MySQL, PostgreSQL, SQLite
  3. 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

go
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

  1. Tách biệt Model và Policy: Giữ file model và policy riêng biệt để dễ quản lý
  2. 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
  3. Kiểm tra hiệu suất: Sử dụng cache cho các kiểm tra quyền thường xuyên
  4. Audit logging: Ghi log các quyết định truy cập để audit
  5. 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.

Golang by www.golangdev.cn edit