Casbin 是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型。
write-article
, read-log
等权限。 它不控制对特定文章或日志的访问。resource.Owner
这种语法糖获取元素的属性。/res/*
, /res/: id
和 HTTP 方法, 如 GET
, POST
, PUT
, DELETE
。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
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
本小结代码照抄 harbor 的源码, 是我为了了解 harbor 机制时时抽离简化的代码。具体解析可以看我的harbor的访问控制.
快速过以下代码, 具体解析那边文章讲过。
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()
}
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")
)
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 == '*')
`
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
}
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}}
}
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,
}
}
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)
}
}
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 中的源码什么都通了。
github