First, link to casbin’s official website
What can Casbin do?
Support for multiple programming languages Go/Java/Node/PHP/Python/.NET/Rust, more than a learning to use
- Support custom request formats, default formats (Subject, Object, Action)
- It has two core concepts: access control model and policy
- Multiple layers of inheritance in RBAC are supported. Not only subject can have roles, but object can also have roles
- Built-in super users, such as root or admin, are supported. Super users can perform any operation without explicit permission declaration
- Support for various built-in operators, such as keyMatch, to facilitate the management of path-like resources, such as /foo/bar can be mapped to /foo*
What can’t Casbin do?
- Authentication (that is, authenticating the user name and password), Casbin is only responsible for access control. There should be some other specialized component responsible for authentication, and then access control by Casbin, and the two work together.
- Manage the user list or role list. Casbin thinks it is more appropriate for the project itself to manage the list of users and roles. Users usually have their passwords, but Casbin is not designed as a container for storing passwords. Instead, it stores mappings between users and roles in the RBAC schema
Quick start
Casbin uses configuration files to set access control modes.
It has two configuration files, model.conf and policy.csv. Where model.conf stores the access model and policy.csv stores the specific user permission configuration. The use of Casbin is very refined. Basically, we just need one main structure: enforcer. When this structure is built, model.conf and policy.csv will be loaded
mkdir demo && cd demo && go mod init github.com/51op/go-sdk-demo
go get github.com/casbin/casbin/v2
Copy the code
- Create the model file model.conf
[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, The act [matchers] m = r.s ub = = p. ub && r.o bj bj & & state Richard armitage ct = = = = p.o p.a ct | | r.s ub = = "root" # as long as the access to the main body is the root all release. [policy_effect] e = some(where (p.eft == allow))Copy the code
The above model file specifies that the permission consists of sub, OBj, and ACT. The request can be approved only if there is a policy that is identical with it in the policy list. The result of the matcher can be obtained by p.ft, some(where (p.ft == allow)) as long as a policy allows it
- Create policy control file policy.csv
P, demo, /user, write #demo user has write permission on /user p, demo, /user, /order, read #demo user has read permission on /order p, demo1, /user/userlist,read #demo1 User has read permission for /user/ userList p, demo2, /order/ orderList,read #demo2 user has read permission for /order/ orderListCopy the code
- Check the permissions
import (
"fmt"
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
_ "github.com/go-sql-driver/mysql"
"log"
"testing"
)
func CheckPermi(e *casbin.Enforcer ,sub,obj,act string) {
ok, err := e.Enforce(sub, obj, act)
iferr ! =nil {
return
}
if ok == true {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
func TestCasBin( t *testing.T) {
e, err := casbin.NewEnforcer("./model.conf"."./policy.csv")
iferr ! =nil{
log.Fatalf("NewEnforecer failed:%v\n", err)
}
// Set basic permissions
CheckPermi(e, "demo"."/user"."read")
CheckPermi(e, "demo"."/order"."write")
CheckPermi(e, "demo1"."/user/userlist"."read")
CheckPermi(e, "demo1"."/order/orderlist"."write")}Copy the code
You can view the results directly by running the editor on the official website.
Practical projectreference
Some code is not extracted do not spray
- The directory structure is as follows:
Dnspod / ├ ─ ─ API │ ├ ─ ─ casbiniapi. Go │ ├ ─ ─ internal │ │ └ ─ ─ model │ │ └ ─ ─ casbin. Go ├ ─ ─ the config │ ├ ─ ─ config. The json │ ├ ─ ─ Config. Yaml │ └ ─ ─ model. The conf ├ ─ ─ common │ ├ ─ ─ global. Go ├ ─ ─ Dockerfile ├ ─ ─ docs │ ├ ─ ─ docs. Go │ ├ ─ ─ swagger. Json │ └ ─ ─ Swagger. Yaml ├ ─ ─ go. Mod ├ ─ ─. Sum ├ ─ ─ main. Go ├ ─ ─ the router │ ├ ─ ─ handler │ │ └ ─ ─ func. Go │ └ ─ ─ the route. GoCopy the code
- First of all in
Created in the configs directory
Conf file with the following code:
[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 = r.sub == p.sub && r.obj == p.obj || ParamsMatch(r.obj,p.obj) && r.act == p.actCopy the code
- in
api/internal/model
In the directory, create acasbin.go
file
package model
import (
"dnspod/common"
_ "dnspod/common"
"log"
)
type CasinoModel struct {
PType string 'GORm :"column:p_type" JSON :"p_type" Form :"p_type" Description :" Policy type"'
RoleId string 'GORm :"column:v0" JSON :"role_id" form:"v0" Description :"role ID "'
Path string 'GORm :"column:v1" JSON :"path" form:"v1" Description :" API path"'
Method string 'GORm :"column:v2" JSON :"method" form:"v2" Description :"method"
}
func(c *CasinoModel) TableName(a) string {
return "casbin_rule"
}
func ( c *CasinoModel) AddPolicy(a) error {
ifok,_:=common.CasBin.AddPolicy(c.RoleId,c.Path,c.Method); ok==false{
return common.JsonResponse(100."Failed to add policy")}return common.JsonResponse(200."Added strategy succeeded")}Copy the code
- Add the following to the global.go file in the common/ directory:
func InitCasbinDB(a) *casbin.Enforcer {
dsn:=fmt.Sprintf("%s:%s@tcp(%s)/",cfg.MySQL.Username,cfg.MySQL.Password,cfg.MySQL.Host)
adapter, _ := gormadapter.NewAdapter("mysql", dsn,)
CasBin, _ = casbin.NewEnforcer(cfg.CasBin.FilePath, adapter)
CasBin.AddFunction("ParamsMatch",ParamsMatchFunc)
CasBin.LoadPolicy()
return CasBin
}
func ParamsMatch(fullNameKey1 string,key2 string) bool {
key1 := strings.Split(fullNameKey1, "?") [0]
return util.KeyMatch2(key1,key2)
}
// Register func to casbin
func ParamsMatchFunc(args ...interface{})(interface{},error) {
name1 := args[0]. (string)
name2 := args[1]. (string)
return ParamsMatch(name1, name2), nil
}
Copy the code
-
Add a request to route.go under router/
common.InitCasbinDB() // Initialize InitCasbinDB //Casbin permission authentication authGroup:=router.Group("/api/v1/auth") { authGroup.POST("/addPolicy",handler.AddPolicy) } Copy the code
-
Add AddPolicy to func.go in the router/ Handler directory
//Casbin permission management
type CasbinInfo struct {
Path string `json:"path" form:"path"`
Method string `json:"method" form:"method"`
}
type CasbinCreateRequest struct {
RoleId string 'json:"role_id" form:"role_id" Description :" Role ID"'
CasbinInfos []CasbinInfo 'JSON :"casbin_infos" description:" Permission model list "'
}
func AddPolicy(c *gin.Context ) {
log.Printf("= = = = = = = = = =")
var params CasbinCreateRequest
c.ShouldBind(¶ms)
for _, v := range params.CasbinInfos {
log.Println(params.RoleId, v.Path, v.Method)
err := api.AddPolicyApi(params.RoleId, v.Path, v.Method)
iferr ! =nil {
// c.JSON(http.StatusOK,gin.H{
// "res":"bad",
// })
}
}
c.JSON(http.StatusOK,gin.H{
"res":"ok"})},Copy the code
- Create the casbiniapi.go file in the API/directory
package api
import "dnspod/api/internal/model"
func AddPolicyApi(roleId string, path, method string) error {
p:=model.CasinoModel{
PType: "p",
RoleId: roleId,
Path: path,
Method: method,
}
p.AddPolicy()
return nil
}
Copy the code
Finally start the project
validation
Check the mysql database to find the data
- Normal service logic is processed after permission authentication succeeds
Adding access points
authGroup.GET("/testPolicy",common.CasbinMiddleware(),handler.TestListPolicics)
Copy the code
Test business logic
func TestListPolicics(c *gin.Context) {
c.JSON(http.StatusOK,common.Reponse{200."Permission through normal business logic".""})}Copy the code
Add casbin middleware
/ / casbin middleware
func CasbinMiddleware(a) gin.HandlerFunc {
return func(c *gin.Context) {
r:=NewResponseContext(c)
path:=c.Request.URL.RequestURI() //
method:=c.Request.Method
log.Println(path,method)
// Verify URL permissions
roleId:="admin"
ok, _ := CasBin.Enforce(roleId, path, method)
if ok {
c.Next()
}else {
c.Abort()
r.ResponseContextMsg("Unfortunately, the permission verification failed.")
return}}}Copy the code
Mysql > execute this API if there is no p/API /v1/auth/testPolicy GET policy
Add API /v1/auth/addPolicy permission
/ API /v1/auth/testPolicy has permissions
-
Example Query the permission of a role
authGroup.POST("/listPolicy",handler.GetListPolicy) // Get all policies for the current user Copy the code
func GetListPolicy( c *gin.Context) {
params:=CasbinRequestRoleId{}
c.ShouldBind(¶ms)
res:=api.ListPolicyApiByRoleId(params.RoleId)
log.Println(res)
c.JSON(http.StatusOK,common.Reponse{200."Get ahead",res})
}
Copy the code
func ListPolicyApiByRoleId(roleId string)[] []string {
r:=model.CasinoModel{RoleId: roleId}
return r.ListPolicyByRoleId(roleId)
}
Copy the code
Authentication based on RBAC permission
Model The matcher to be repaired in the model is as follows:
/ / use keyMatch function, this kind of circumstance can transfer parameters of the user to match different user permissions, / API/v1 / auth/testPolicy/user1, / API/v1 / auth/testPolicy/user2
[matchers]
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && r.act == p.act
Copy the code
- Increase the role of
Assign a single role to a user or assign multiple roles to a user
Assign a single role to a single user
func (c *CasinoModel) AddRoleForUser(user string,roles string) error {
ifok, _ := common.CasBin.AddRoleForUser(user, roles); ! ok{return errors.New("Failed to assign user a single role")}return nil
}
// Assign multiple roles to a single user
func (c *CasinoModel) AddRolesForUser(user string,roles []string) error {
ifok, _ := common.CasBin.AddRolesForUser(user, roles); ! ok{return errors.New("Roles policy already in the database")}return nil
}
Copy the code
Add a route to the./router/handler/func.go directory
type UserRoleInfo struct {
UserName string `json:"user_name"`
RoleName string `json:"role_name"`
}
func AddRoleUser(c *gin.Context) {
u:= UserRoleInfo{}
c.ShouldBind(&u)
iferr:=api.AddRoleForUserApi(u.UserName,u.RoleName); err ! =nil{
common.ErrorResp(c,http.StatusInternalServerError,"Failed to add role to user")
return
}
common.SuccessResp(c,"Adding role to user succeeded")}// Assign multiple roles to a single user
type RolesInfoRequest struct {
UserName string `json:"user_name"`
RoleName []string `json:"role_name"`
}
func AddRolesUser(c *gin.Context) {
ro:= RolesInfoRequest{}
err:=c.ShouldBind(&ro)
iferr ! =nil {
panic(err)
}
iferr:=api.AddRolesForUserApi(ro.UserName,ro.RoleName); err! =nil{
common.ErrorResp(c,http.StatusInternalServerError,"Failed to assign multiple roles to user")
return
}
common.SuccessResp(c,"Assigning multiple roles to user succeeded")}Copy the code
Add a route to router/route.go
authGroup.POST("/AddRoleUser",handler.AddRoleUser)
authGroup.POST("/AddRolesUser",handler.AddRolesUser)
Copy the code
- Verify the RBAC
Look at the database, and you have a corresponding record
-
Assign permissions to the test URI to role Member
Check the corresponding records in the database
Request/API/v1 / auth/testPolicy/user1 and/API/v1 / auth/testPolicy/user2
The user1 authentication fails
User2 through
【 Follow me 】 Keep updating ing…