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 inCreated in the configs directoryConf 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
  • inapi/internal/modelIn the directory, create acasbin.gofile
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(&params)

	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(&params)
	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…