一。 开始

Casbin 是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型。

write-articleread-logresource.Owner/res/*/res/: idGETPOSTPUTDELETE
二。 使用(最简单的 demo) ACL 模式
model.conf
# 要去请求的资源sub(subject) 请求的实体
[request_definition]
r = sub, obj, act
# 表示拥有的权限
[policy_definition]
p = sub, obj, act, eft
# policy 生效的范围  其中p.eft 表示策略规则的决策结果,可以为allow 或者deny,当不指定规则的决策结果时,取默认值allow 。
#通常情况下,policy的p.eft默认为allow
#该Effect原语表示当至少存在一个决策结果为allow的匹配规则,且不存在决策结果为deny的匹配规则时,则最终决策结果为allow。
#这时allow授权和deny授权同时存在,但是deny优先。
[policy_effect]
e = some(where (p.eft == allow))
# Matchers
# 匹配器用于上面 请求和策略的匹配
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
model.csv
p, Archer, configuration, list, allow
  • model.conf 文件声明了 casbin 如何去进行访问控制, 你的请求参数是啥,策略是啥,如何匹配
  • model.csv 声明了用户的权限,casbin,根据该文件的内容与请求参数进行对比

main 函数:

package main

import (

	"fmt"
	"github.com/casbin/casbin"
)

var (
	conf = "C:\\Users\\Vic\\go\\src\\github.com\\Archer1A\\casbin-demo\\model.conf"
	csv = "C:\\Users\\Vic\\go\\src\\github.com\\Archer1A\\casbin-demo\\model.csv"

)
func main()  {
	e,_ :=casbin.NewEnforcer(conf,csv)
	if res,_ := e.Enforce("Archer","configuration","list");res{ // archer 是否有list configuration 权限
		fmt.Println("Archer can list configuration")
	}else {
		fmt.Println("Archer can't list configuration")
	}
	if res,_ := e.Enforce("Archer","configuration","operate");res{ // archer 是否有list configuration 权限
		fmt.Println("Archer can operate configuration")
	}else {
		fmt.Println("Archer can't operate configuration")
	}
}

输出结果:

Archer can list configuration
Archer can't operate configuration
三。 支持资源角色的 rbac

本小结代码照抄 harbor 的源码, 是我为了了解 harbor 机制时时抽离简化的代码。具体解析可以看我的harbor的访问控制.

快速过以下代码, 具体解析那边文章讲过。

  1. 实体类文件
package rbac

type Action string
type Resource string
type Effect string

const (
	// allow effect
	EffectAllow = Effect("allow")
	// deny effect
	EffectDeny = Effect("deny")
)



type Policy struct {
	Action
	Resource
	Effect
}

type Role interface {
	GetRoleName() string
	GetPolicies() []*Policy
}

type User interface {
	GetUserName() string
	GetPolicies() []*Policy
	GetRoles() []Role
}

func (eff Effect)String() string  {
	return string(eff)
}

func (resource Resource)String()string  {
	return string(resource)
}

func (action Action)String()string  {
	return string(action)

}

func (p *Policy)GetEffect()string  {
	eft := p.Effect
	if eft.String() == "" {
		return EffectAllow.String()
	}
	return eft.String()

}

  1. 常量文件 写了以下关于资源权限的常量, 资源权限这个 harbor 是写死在代码里面的,后续自己工作用到这个框架为了更好的扩展应该将这些东西放到数据库中
const (
	ActionAll = Action("*") // action match any other actions

	ActionPull = Action("pull") // pull repository tag
	ActionPush = Action("push") // push repository tag

	// create, read, update, delete, list actions compatible with restful api methods
	ActionCreate = Action("create")
	ActionRead   = Action("read")
	ActionUpdate = Action("update")
	ActionDelete = Action("delete")
	ActionList   = Action("list")

	ActionOperate = Action("operate")
)

const (
	ResourceAll = Resource("*")
	ResourceConfiguration = Resource("configuration")
	ResourceRepository = Resource("repository")
)
  1. 实现 casbin 的 adaptor 接口, 通过接口将用户的所有权限导入到 casbin 中(等同于前一小结中的 model.csv 文件)

type userAdapter struct {
	User
}

// 获取role的所有策略
func (u *userAdapter)getRoleAllPoliciesLine(role Role) []string  {
	lines := []string{}
	name := role.GetRoleName()
	if name == "" {
		return lines
	}
	for _ ,policy := range role.GetPolicies() {
		line := fmt.Sprintf("p, %s, %s, %s, %s",name,policy.Resource,policy.Action,policy.GetEffect())
		lines = append(lines,line)
	}
	return lines
}
// 获取绑定在user 上的权限(不包括role)
func (u *userAdapter)getUserPoliciesLine() []string {
	lines := []string{}
	userName := u.GetUserName()
	if userName == "" {
		return lines
	}
	for _,policy := range u.GetPolicies(){
		line := fmt.Sprintf("p, %s, %s, %s, %s",userName,policy.Resource,policy.Action,policy.GetEffect())
		lines = append(lines, line)
	}
	return lines
}
// 获取该用户的所有权限(包括role)
func (u *userAdapter)getUserAllPoliciesLine() []string {
	lines := []string{}
	userName := u.GetUserName()
	if userName == "" {
		return lines
	}
	lines = append(lines, u.getUserPoliciesLine()...)
	for _,role := range u.GetRoles() {
		lines = append(lines, u.getRoleAllPoliciesLine(role)...)
		lines = append(lines, fmt.Sprintf("g, %s, %s",userName,role.GetRoleName()))

	}
	return lines
}

type unImplementError  error

func (u *userAdapter)LoadPolicy(model model.Model) error  {
	lines := u.getUserAllPoliciesLine()
	for _,line := range lines{
		persist.LoadPolicyLine(line,model)
	}
	return nil

}


func (u *userAdapter)SavePolicy(model model.Model)error  {
	return unImplementError(errors.New(""))

}
func (u *userAdapter)AddPolicy(sec string, ptype string, rule []string) error  {
	return unImplementError(errors.New(""))

}
func (u *userAdapter)RemovePolicy(sec string, ptype string, rule []string) error  {
	return unImplementError(errors.New(""))

}

func (u *userAdapter)RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error  {
	return unImplementError(errors.New(""))

}

创建一个常量 casbinModel, 其等同于 model.conf

const casbinModel = `
# Request definition
# 要去请求的资源sub(subject) 请求的实体
[request_definition]
r = sub, obj, act

# Policy definition 
# 表示拥有的权限
# 例如: p, vic, /project/1/member, list, allow  表示vic用户拥有获取project id=1的项目 的下成员的列表权限
[policy_definition]
p = sub, obj, act, eft

# Role definition
# 代表着role 的所属关系
# 例如: g, vic, admin  表示vic 拥有admin 权限
[role_definition]
g = _, _

# Policy effect
# policy 生效的范围  其中p.eft 表示策略规则的决策结果,可以为allow 或者deny,当不指定规则的决策结果时,取默认值allow 。
#通常情况下,policy的p.eft默认为allow
#该Effect原语表示当至少存在一个决策结果为allow的匹配规则,且不存在决策结果为deny的匹配规则时,则最终决策结果为allow。
#这时allow授权和deny授权同时存在,但是deny优先。
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))

# Matchers
# 匹配器用于上面 请求和策略的匹配
[matchers]
m = g(r.sub, p.sub) && (r.act == p.act || p.act == '*')
`
  1. userAdaptor 和 conf 注册到 casbin 中
func enforceForUser(user User) *casbin.Enforcer {
	m := model.Model{}
	m.LoadModelFromText(casbinModel)
	//csv := "C:\\Users\\Vic\\go\\src\\github.com\\Archer1A\\casbin-demo\\model.csv"
	//a := fileadapter.NewAdapter(csv)
	e,_ := casbin.NewEnforcer(m,&userAdapter{User:user})
	return e
}
  1. 实现 1 中的 user 和 role 接口,并声明资源和权限的组合
package project

import "github.com/Archer1A/casbin-demo/harbor/rbac"

type VisitorRole struct {
	roleId int
}


var policy  = map[string][]*rbac.Policy{
	"admin":{
		&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionPull}, // pull 权限
		&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionPush}, // push
		&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionList}, // list
		&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionDelete}, // delete
		&rbac.Policy{Resource: rbac.ResourceConfiguration,Action:rbac.ActionList}, // list configuration
		&rbac.Policy{Resource: rbac.ResourceConfiguration,Action:rbac.ActionUpdate}, // update Configuration
	},
	"master":{
		&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionList},
		&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionPull},
		&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionList},
		&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionDelete},
	},
	"dev":{
		&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionList},
		&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionPull},
	},
}

type Visitor struct {
	UserName string
	Role int
}

var publicPolicy = []*rbac.Policy{
	&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionPull},
	&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionPush},
	&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionList},
	&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionDelete},
}

var privatePolicy = []*rbac.Policy{
	&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionPull},
	&rbac.Policy{Resource: rbac.ResourceRepository,Action:rbac.ActionList},
}

func (role *VisitorRole)GetRoleName() string  {
	switch role.roleId {
	case 1:
		return "admin"
	case 2:
		return "master"
	case 3:
		return "dev"
	case 4:
		return "guest"
	default:
		return ""
	}
}

func (role *VisitorRole)GetPolicies() []*rbac.Policy {
	roleName := role.GetRoleName()
	return policy[roleName]
}


func (vi *Visitor)GetUserName() string{
	return vi.UserName
}
func (vi *Visitor)GetPolicies() []*rbac.Policy{
	if vi.GetUserName() == "alice" {
		return publicPolicy
	}else {
		return privatePolicy
	}
}
func (vi *Visitor)GetRoles() []rbac.Role{
	return []rbac.Role{&VisitorRole{roleId:vi.Role}}
}



  1. 访问控制入口
package rbac

import (
	"fmt"
	"github.com/casbin/casbin"
	"sync"
)

type evaluator struct {
	user User
	enforcer *casbin.Enforcer
	once sync.Once
}

func (e *evaluator)HasPermission(resource Resource,action Action) bool  {
	e.once.Do(func() {
		e.enforcer = enforceForUser(e.user)
	})
	b, err := e.enforcer.Enforce(e.user.GetUserName(), resource.String(), action.String())
	if err != nil {
		fmt.Println(err.Error())
	}

	return b
}

func NewEvaluator(user User) *evaluator {
	return &evaluator{
		user: user,
	}
}
  1. main 函数
package main

import (
	"fmt"
	"github.com/Archer1A/casbin-demo/harbor/rbac"
	"github.com/Archer1A/casbin-demo/harbor/rbac/project"
	"sync"
)

var once sync.Once
func main()  {
	admin := ≺oject.Visitor{
		UserName: "Archer",
		Role: 1,
	}
	evaluate(admin,rbac.ResourceRepository,rbac.ActionList)
	evaluate(admin,rbac.ResourceRepository,rbac.ActionRead)
	evaluate(admin,rbac.ResourceRepository,rbac.ActionPush)
	evaluate(admin,rbac.ResourceRepository,rbac.ActionPull)
	evaluate(admin,rbac.ResourceRepository,rbac.ActionCreate)
	evaluate(admin,rbac.ResourceRepository,rbac.ActionDelete)

	saber := ≺oject.Visitor{
		UserName: "Saber",
		Role:     2,
	}
	evaluate(saber,rbac.ResourceRepository,rbac.ActionList)
	evaluate(saber,rbac.ResourceRepository,rbac.ActionRead)
	evaluate(saber,rbac.ResourceRepository,rbac.ActionPush)
	evaluate(saber,rbac.ResourceRepository,rbac.ActionPull)
	evaluate(saber,rbac.ResourceRepository,rbac.ActionCreate)
	evaluate(saber,rbac.ResourceRepository,rbac.ActionDelete)
	
	alice := ≺oject.Visitor{
		UserName: "alice",
		Role:     0,
	}

	evaluate(alice,rbac.ResourceRepository,rbac.ActionList)
	evaluate(alice,rbac.ResourceRepository,rbac.ActionRead)
	evaluate(alice,rbac.ResourceRepository,rbac.ActionPush)
	evaluate(alice,rbac.ResourceRepository,rbac.ActionPull)
	evaluate(alice,rbac.ResourceRepository,rbac.ActionCreate)
	evaluate(alice,rbac.ResourceRepository,rbac.ActionDelete)

}

func evaluate(visit *project.Visitor,resource rbac.Resource,action rbac.Action)  {

	adminEvaluator := rbac.NewEvaluator(visit)
	if  (adminEvaluator.HasPermission(resource,action)){
		fmt.Printf("%s role %d have %s %s permission \n", visit.UserName,visit.Role,resource.String(),action)
	}else {
		fmt.Printf("%s role %d  dont have %s %s permission \n", visit.UserName,visit.Role,resource.String(),action)
	}
}
  1. 输出结果
Archer role 1 have repository list permission 
Archer role 1  don't have repository read permission 
Archer role 1 have repository push permission 
Archer role 1 have repository pull permission 
Archer role 1  don't have repository create permission 
Archer role 1 have repository delete permission 
Saber role 2 have repository list permission 
Saber role 2  don't have repository read permission 
Saber role 2  don't have repository push permission 
Saber role 2 have repository pull permission 
Saber role 2  don't have repository create permission 
Saber role 2 have repository delete permission 
alice role 0 have repository list permission 
alice role 0  don't have repository read permission 
alice role 0 have repository push permission 
alice role 0 have repository pull permission 
alice role 0  don't have repository create permission 
alice role 0 have repository delete permission 
四。 总结

从 Java 到 golang, 自己写 golang,总是习惯用 Java 的方式,总感觉自己写的 go 和别人的不一样。通过这个 demo, 学习到的东西挺多的, 结构体怎么用,接口该怎么用等 harbor 这个访问控制流程还是比较清晰的运行一遍这个,再去看 harbor 中的源码什么都通了。