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的访问控制.
快速过以下代码, 具体解析那边文章讲过。
- 实体类文件
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()
}
- 常量文件 写了以下关于资源权限的常量, 资源权限这个 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")
)
- 实现 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 == '*')
`
- 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 中的 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}}
}
- 访问控制入口
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,
}
}
- 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)
}
}
- 输出结果
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 中的源码什么都通了。