Team author: Jock
What is Casbin
Casbin is a powerful and efficient open source access control framework. Its permission management mechanism supports multiple access control models. Casbin is only responsible for access control.
Its functions are:
- Supports custom request format. The default request format is
{subject, object, action}
. - It has two core concepts: access control model and policy.
- Multiple layers of role inheritance are supported in RBAC, where not only principals can have roles, but also resources.
- Support for built-in superusers such as:
root
oradministrator
. A superuser can perform any operation without explicit permission declarations. - Supports a variety of built-in operators, such as
keyMatch
To facilitate the management of path-based resources, for example/foo/bar
Can be mapped to/foo*
How Casbin works
In Casbin, the access control model is abstracted as a file based on **PERM **(Policy, Effect, Request, Matcher) [Policy, Effect, Request, Matcher].
- Policy: defines permission rules
- Effect: Defines the result of combining multiple policies
- Request: indicates an access Request
- Matcher: checks whether the Request meets the Policy
Matcher is used to determine whether the Request and Policy match. Effect is used to determine whether the match result is Allow or Deny.
The core concept of Casbin
Model
Model is the concrete access Model of Casbin, which is mainly in the form of a file, usually with a.conf suffix.
- The Model CONF should contain at least four parts:
[request_definition]
.[policy_definition]
.[policy_effect]
.[matchers]
. - If the Model uses RBAC, you need to add it
[role_definition]
Part. - The Model CONF file can contain comments. Comments begin with #, which comments the rest of the line.
Such as:
# Request definition
[request_definition]
r = sub, obj, act
# Policy definition
[policy_definition]
p = sub, obj, act
# Role definition
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft = = allow))
# matcher definition
[matchers]
m = g(r.sub, p.sub) && r.obj = = p.obj && r.act = = p.act
Copy the code
- Request_definition: The definition used for request, which specifies e.enforce (…) The definition of parameters in a function,
sub, obj, act
Represents classical triples: access entities (Subject), access resources (Object), and access methods (Action). - Policy_definition: used to define a policy. Each rule is usually defined as follows
p
thepolicy type
At the beginning, likep,joker,data1,read
Is a rule that the joker has datA1 read permission. - Role_definition: defines the RBAC role inheritance relationship.
g
Is an RBAC system,_, _
Indicates the preceding and subsequent items of the role inheritance relationship, that is, the permission of the former item to inherit the latter role. - Policy_effect: defines the scope of a policy that makes a unified decision on the outcome of a request, for example
e = some(where (p.eft == allow))
It means that if there exists any decision result ofallow
, then the final decision result isallow
.p.eft
Represents the decision result of the policy rule, which can beallow
ordeny
When the decision result of the rule is not specified, the default value is usedallow
。 - Matchers: Defines policy matchers. A matcher is a set of expressions that define how to match policy rules against requests
Policy
A Policy specifies the mapping between roles, resources, and behaviors.
Such as:
p, alice, data1, read
p, bob, data2, write
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, data2_admin
Copy the code
Its relational rules are very simple, mainly to choose which way to store rules, currently officially provides CSV file storage and load configuration files from other storage systems through adapter adapter. For example, MySQL, PostgreSQL, SQL Server, SQLite3, directing, Redis, Cassandra DB, etc.
practice
Create a project
Start by creating a project called casbin_test.
The directory structure in the project is as follows:
├ ─ configs # config file
├ ─ global # global variables
├ ─ internal # Internal module
│ ├ ─ dao # Data processing module
│ ├ ─ middleware # middleware
│ ├ ─ model # model layer
│ ├ ─ the router # routing
│ │ └ ─ API
│ │ └ ─ v1 # view
│ └ ─ service # Business logic layer
└ ─ PKG # Internal module package
├ ─ app # of packages
├ ─ errcode Error code package
└ ─ setting # configuration package
Copy the code
Download the dependency packages as follows:
go get -u github.com/gin-gonic/gin
# Go casbin dependencies
go get github.com/casbin/casbin
# GORm adapter dependency package
go get github.com/casbin/gorm-adapter
Mysql driver dependencies
go get github.com/go-sql-driver/mysql
# gorm package
go get github.com/jinzhu/gorm
Copy the code
Create a database as follows:
CREATE DATABASE `casbin_test` DEFAULT CHARACTER SET utf8;
GRANT Alter.Alter Routine, Create.Create Routine, Create Temporary Tables, Create View.Delete.Drop, Event, Execute, Index, Insert, Lock Tables, References.Select.Show View.Trigger, Update ON `casbin\_test`.* TO `ops`@`%`;
FLUSH PRIVILEGES;
DROP TABLE IF EXIST `casbin_rule`;
CREATE TABLE `casbin_rule` (
`p_type` varchar(100) DEFAULT NULL COMMENT 'Rule type',
`v0` varchar(100) DEFAULT NULL COMMENT 'character ID',
`v1` varchar(100) DEFAULT NULL COMMENT 'API path',
`v2` varchar(100) DEFAULT NULL COMMENT 'API access method',
`v3` varchar(100) DEFAULT NULL,
`v4` varchar(100) DEFAULT NULL,
`v5` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Permission Rule List';
/* Insert permission rules for operation casbin API */
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`) VALUES ('p'.'admin'.'/api/v1/casbin'.'POST');
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`) VALUES ('p'.'admin'.'/api/v1/casbin/list'.'GET');
Copy the code
Code development
Due to the large number of codes, I will not post all the codes here. All the codes have been put in the Gitee warehouse and can be read by myself. These only post part of the key codes.
(1) Create the rbac_model.conf file in the configs directory.
[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 && ParamsMatch(r.obj,p.obj) && r.act == p.actCopy the code
(2) Create casbin.go file in internal/model directory and write the following code:
type CasbinModel struct {
PType string 'JSON :"p_type" GORm :"column:p_type" Description :" Policy type"'
RoleId string 'JSON :"role_id" GORm :"column:v0" Description :" Role ID"'
Path string 'JSON :"path" GORm :"column:v1" Description :" API path"'
Method string 'JSON :"method" GORm :"column:v2" Description :" Access method"'
}
func (c *CasbinModel) TableName(a) string {
return "casbin_rule"
}
func (c *CasbinModel) Create(db *gorm.DB) error {
e := Casbin()
if success := e.AddPolicy(c.RoleId,c.Path,c.Method); success == false {
return errors.New("Same API exists, add failed")}return nil
}
func (c *CasbinModel) Update(db *gorm.DB, values interface{}) error {
if err := db.Model(c).Where("v1 = ? AND v2 = ?", c.Path, c.Method).Update(values).Error; err ! =nil {
return err
}
return nil
}
func (c *CasbinModel) List(db *gorm.DB)[] []string {
e := Casbin()
policy := e.GetFilteredPolicy(0, c.RoleId)
return policy
}
//@function: Casbin
//@description: Persisting to the database introduces custom rules
//@return: *casbin.Enforcer
func Casbin(a) *casbin.Enforcer {
s := fmt.Sprintf("%s:%s@tcp(%s)/%s? charset=%s&parseTime=%t&loc=Local",
global.DatabaseSetting.Username,
global.DatabaseSetting.Password,
global.DatabaseSetting.Host,
global.DatabaseSetting.DBName,
global.DatabaseSetting.Charset,
global.DatabaseSetting.ParseTime,
)
db, _ := gorm.Open(global.DatabaseSetting.DBType, s)
adapter := gormadapter.NewAdapterByDB(db)
enforcer := casbin.NewEnforcer(global.CasbinSetting.ModelPath, adapter)
enforcer.AddFunction("ParamsMatch", ParamsMatchFunc)
_ = enforcer.LoadPolicy()
return enforcer
}
//@function: ParamsMatch
//@description: custom rule function
//@param: fullNameKey1 string, key2 string
//@return: bool
func ParamsMatch(fullNameKey1 string, key2 string) bool {
key1 := strings.Split(fullNameKey1, "?") [0]
// Use casbin's keyMatch2 after stripping the path
return util.KeyMatch2(key1, key2)
}
//@function: ParamsMatchFunc
//@description: custom rule function
//@param: args ... interface{}
//@return: interface{}, error
func ParamsMatchFunc(args ...interface{}) (interface{}, error) {
name1 := args[0]. (string)
name2 := args[1]. (string)
return ParamsMatch(name1, name2), nil
}
Copy the code
(3) Create casbin.go in internal/ DAO directory and write the following code:
func (d *Dao) CasbinCreate(roleId string, path, method string) error {
cm := model.CasbinModel{
PType: "p",
RoleId: roleId,
Path: path,
Method: method,
}
return cm.Create(d.engine)
}
func (d *Dao) CasbinList(roleID string)[] []string {
cm := model.CasbinModel{RoleId: roleID}
return cm.List(d.engine)
}
Copy the code
Create service.go in internal/service directory.
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 "'
}
type CasbinListResponse struct {
List []CasbinInfo `json:"list" form:"list"`
}
type CasbinListRequest struct {
RoleID string `json:"role_id" form:"role_id"`
}
func (s Service) CasbinCreate(param *CasbinCreateRequest) error {
for _, v := range param.CasbinInfos {
err := s.dao.CasbinCreate(param.RoleId, v.Path, v.Method)
iferr ! =nil {
return err
}
}
return nil
}
func (s Service) CasbinList(param *CasbinListRequest)[] []string {
return s.dao.CasbinList(param.RoleID)
}
Copy the code
Create casbin.go from internal/router/ API /v1
type Casbin struct{}func NewCasbin(a) Casbin {
return Casbin{}
}
// Create godoc
// @summary new permission
// @description new permission
// @tags Permission management
// @Produce json
// @Security ApiKeyAuth
// @Param body body service.CasbinCreateRequest true "body"
// @success 200 {object} string "Success"
// @failure 400 {object} errcode.Error
// @failure 500 {object} errcode.Error "Internal Error"
// @Router /api/v1/casbin [post]
func (c Casbin) Create(ctx *gin.Context) {
param := service.CasbinCreateRequest{}
response := app.NewResponse(ctx)
valid, errors := app.BindAndValid(ctx, ¶m)
if! valid { log.Printf("app.BindAndValid errs: %v", errors)
errRsp := errcode.InvalidParams.WithDetails(errors.Errors()...)
response.ToErrorResponse(errRsp)
return
}
// Insert
svc := service.NewService(ctx)
err := svc.CasbinCreate(¶m)
iferr ! =nil {
log.Printf("svc.CasbinCreate err: %v", err)
response.ToErrorResponse(errcode.ErrorCasbinCreateFail)
}
response.ToResponse(gin.H{})
return
}
// List godoc
// @summary get permission list
// @Produce json
// @tags Permission management
// @Security ApiKeyAuth
/ / @ Param data body service. CasbinListRequest true character ID ""
/ / @ 200 {object} service Success. CasbinListResponse "Success"
// @failure 400 {object} errcode.Error
// @failure 500 {object} errcode.Error "Internal Error"
// @Router /api/v1/casbin/list [post]
func (c Casbin) List(ctx *gin.Context) {
param := service.CasbinListRequest{}
response := app.NewResponse(ctx)
valid, errors := app.BindAndValid(ctx, ¶m)
if! valid { log.Printf("app.BindAndValid errs: %v", errors)
errRsp := errcode.InvalidParams.WithDetails(errors.Errors()...)
response.ToErrorResponse(errRsp)
return
}
// Business logic processing
svc := service.NewService(ctx)
casbins := svc.CasbinList(¶m)
var respList []service.CasbinInfo
for _, host := range casbins {
respList = append(respList, service.CasbinInfo{
Path: host[1],
Method: host[2],
})
}
response.ToResponseList(respList, 0)
return
}
Copy the code
Create a test.go file in this directory for testing.
type Test struct{}func NewTest(a) Test {
return Test{}
}
func (t Test) Get(ctx *gin.Context) {
log.Println("Hello received a GET request..")
response := app.NewResponse(ctx)
response.ToResponse("Received GET request successful")}Copy the code
(6) Create casbin_handler.go in the internal/middleware directory and write:
func CasbinHandler(a) gin.HandlerFunc {
return func(ctx *gin.Context) {
response := app.NewResponse(ctx)
// Get the request URI
obj := ctx.Request.URL.RequestURI()
// Get the request method
act := ctx.Request.Method
// Get the user's role
sub := "admin"
e := model.Casbin()
fmt.Println(obj, act, sub)
// Check whether the policy exists
success := e.Enforce(sub, obj, act)
if success {
log.Println("Congratulations, your permission has been verified.")
ctx.Next()
} else {
log.Printf("e.Enforce err: %s"."Unfortunately, the permission verification failed.")
response.ToErrorResponse(errcode.UnauthorizedAuthFail)
ctx.Abort()
return}}}Copy the code
Create router. Go from internal/router directory to router.
func NewRouter(a) *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
casbin := v1.NewCasbin()
test := v1.NewTest()
apiv1 := r.Group("/api/v1")
apiv1.Use(middleware.CasbinHandler())
{
// Test the route
apiv1.GET("/hello", test.Get)
// Permission policy management
apiv1.POST("/casbin", casbin.Create)
apiv1.POST("/casbin/list", casbin.List)
}
return r
}
Copy the code
Finally, launch the project for testing.
validation
(1) First access the test path, the current situation is not in the permission table, as follows:
(2) Add the test path to the permission list as follows:(3) Then access the test path again as follows:It can also be seen from the log as follows:
Reference Documents:
[1] casbin.org/ [2] casbin.org/docs/zh-CN/… [3] gitee.com/coolops/cas…