前言
CasbinCasbinCasbin
名词理解
/user/1GETPOST
1.安装
Casbinbeego-ORM-Adapter
go get github.com/casbin/casbin
go get github.com/casbin/beego-orm-adapter
beego-ORM-Adapter
2.配置
confcasbin.conf
conf/casbin.conf
[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 = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
然后假设我们Beego已经注册好MySQL链接,如下所示:
err := orm.RegisterDataBase("default", "mysql", dbLink, maxIdle, maxConn)
beego-ORM-Adapter
3.Casbin数据库模型
Casbin
models/Casbin.go
type CasbinRule struct {
Id int // 自增主键
PType string // Policy Type - 用于区分 policy和 group(role)
V0 string // subject
V1 string // object
V2 string // action
V3 string // 这个和下面的字段无用,仅预留位置,如果你的不是
V4 string // sub, obj, act的话才会用到
V5 string // 如 sub, obj, act, suf就会用到 V3
}
然后直接通过orm注册模型,并migrate到数据库中,这个和其他模型一样
models/Casbin.go
func init(){
orm.RegisterModel(new(CasbinRule))
_ = orm.RunSyncdb("default", false, false)
}
4.Adapter
Adapter
beego-ORM-AdapterNewAdapter
func NewAdapter(driverName string, dataSourceName string, dbSpecified ...bool) *Adapter {
a := &Adapter{}
a.driverName = driverName
a.dataSourceName = dataSourceName
.....
// Open the DB, create it if not existed.
// 打开一个Database链接,如果不存在
a.open()
.....
return a
}
func (a *Adapter) open() {
.....
err = a.registerDataBase("default", a.driverName, a.dataSourceName)
if err != nil {
panic(err)
}
.....
a.o = orm.NewOrm()
a.o.Using("casbin")
a.createTable()
}
NewAdapteropenopenbeego-ORM-AdapterAdapterorm.OrmerAdapter
/models/Casbin.go
// 注意,这个Enforcer很重要,Casbin使用都是调用这个变量
var Enforcer *casbin.Enforcer
type Adapter struct {
o orm.Ormer
}
func RegisterCasbin() {
a := &Adapter{}
a.o = orm.NewOrm()
// 这个我不知道干嘛的
runtime.SetFinalizer(a, finalizer)
// Enforcer初始化 - 即传入Adapter对象
Enforcer = casbin.NewEnforcer("conf/casbin.conf", a)
// Enforcer读取Policy
err := Enforcer.LoadPolicy()
if err != nil {
panic(err)
}
}
// finalizer is the destructor for Adapter.
// 这个函数里面啥都没有,就是这样
func finalizer(_ *Adapter) {}
beego-ORM-Adapter
/models/Casbin.go
// 注意,方法对应的具体代码要从beego-ORM-Adapter/adapter.go中复制过来
// 这里方法里面使用的orm操作还是要根据自己的
// 实际情况作出调整,不要盲目复制
func loadPolicyLine(line CasbinRule, model model.Model){}
func savePolicyLine(ptype string, rule []string) CasbinRule{}
func (a *Adapter) LoadPolicy(model model.Model) error{}
func (a *Adapter) SavePolicy(model model.Model) error{}
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error{}
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error{}
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error{}
savePolicyLinea.dropTable()a.createTable()
func (a *Adapter) SavePolicy(model model.Model) error {
var lines []CasbinRule
for ptype, ast := range model["p"] {
for _, rule := range ast.Policy {
line := savePolicyLine(ptype, rule)
lines = append(lines, line)
}
}
for ptype, ast := range model["g"] {
for _, rule := range ast.Policy {
line := savePolicyLine(ptype, rule)
lines = append(lines, line)
}
}
_, err := a.o.InsertMulti(len(lines), lines)
return err
}
RegisterCasbin()
/models/Casbin.go
func init(){
orm.RegisterModel(new(CasbinRule))
// 实际上同步数据库在整个Beego项目中只需要执行一次,如果
// 您在别的地方已经同步数据库,这里就不用在执行一次 RunSyncdb
_ = orm.RunSyncdb("default", false, false)
// 初始化 Casbin
RegisterCasbin()
}
Enforce
5.Role角色模型
我采用角色控制访问Restful Api的方法,每个用户都有自己的角色,这是一对多(OneToMany)的关系。直接定义一个角色模型如下:
models/Role.go
type Role struct {
Id int `orm:"auto;pk" description:"角色序号" json:"role_id"`
Name string `orm:"unique" description:"角色名" json:"role_name"`
Users []*User `orm:"reverse(many)" description:"用户列表" json:"users"`
}
func init(){
orm.RegisterModel(new(Role))
// 这里因为在别的地方已经同步过数据库了,就不同步了
}
然后我们还得初始化最基本的三个角色,分别是管理员、用户、匿名(未登陆的用户都是匿名),这个根据你项目实际需求去初始化。
models/Role.go
var (
RoleAdmin = "admin"
RoleUser = "user"
RoleAnonymous = "anonymous"
RolesId = map[string]int{
RoleAdmin: -1,
RoleUser: -1,
RoleAnonymous: -1,
}
)
// 注册角色模型 - 初始化
func RegisterRoles() {
o := orm.NewOrm()
// 这里我通过遍历上面构建的一个字典来写入数据库
// 如果不愿意使用骚操作的话,直接写三个ReadOrCreate就好了
// GetRoleString方法是必须的
for key, _ := range RolesId {
_, id, err := o.ReadOrCreate(&Role{Name: GetRoleString(key)}, "Name")
if err != nil {
panic(err)
}
RolesId[key] = int(id)
}
}
// 这个方法主要用于在Name字段加个前缀role_
func GetRoleString(s string) string {
if strings.HasPrefix(s, "role_") {
return s
}
return fmt.Sprintf("role_%s", s)
}
Namerole_Casbing(role)adminuserNamerole_
Casbin
models/Role.go
// 向Casbin添加角色继承策略规则
func AddRolesGroupPolicy() {
// 普通管理员继承用户
_ = Enforcer.AddGroupingPolicy(GetRoleString(RoleAdmin), GetRoleString(RoleUser))
// 用户继承匿名者
_ = Enforcer.AddGroupingPolicy(GetRoleString(RoleUser), GetRoleString(RoleAnonymous))
}
最后在初始化包的时候调用这两个方法:
func init(){
RegisterRoles()
AddRolesGroupPolicy()
}
6.User模型
User用户名Username密码Password角色Role
models/User.go
type User struct {
// 用户模型
Id int `orm:"auto;pk" description:"用户序号" json:"uid"`
Username string `orm:"unique" description:"用户名" json:"username"`
Password string `description:"用户密码" json:"password"`
Role *Role `orm:"rel(fk);null" description:"角色" json:"Role"`
}
// 各种ORM查询方法请自行实现,这里不强调
7.定义用户控制器Controller
CasbinRestful ApiCasbin管理员用户匿名
controllers/User.go
type UserController struct {
beego.Controller
}
// 只有管理员才能注册
// @router /register [post]
func (c *UserController) Register(){}
// 只有用户、管理员才能看到别人或者自己的个人资料
// 因为管理员继承用户,所以用户能做到的,管理员也可
// @router /profile [get]
func (c *UserController) Profile(){}
// 匿名也能登陆
// @router /login [post]
func (c *UserController) Login(){}
Policy
controllers/User.go
func registerUserPolicy() {
// Path前缀,这个根据具体项目自行调整
api := "/v1/user"
// 路由的Policy
adminPolicy := map[string][]string{
"/register": {"post"},
}
userPolicy := map[string][]string{
// 注意 - casbin.conf中使用 keyMatch2 对 obj 进行
// 验证,这里要使用 :id 来对参数进行标识
"/:id": {"get", "put", "delete"},
}
anonymousPolicy := map[string][]string{
"/login": {"post"},
}
// models.RoleAdmin = "admin"
// models.RoleUser = "user"
// models.RoleAnonymous = "anonymous"
AddPolicyFromController(models.RoleAdmin, adminPolicy, api)
AddPolicyFromController(models.RoleUser, userPolicy, api)
AddPolicyFromController(models.RoleAnonymous, anonymousPolicy, api)
}
func AddPolicyFromController(role string, policy map[string][]string, api string) {
for path := range policy {
for _, method := range policy[path] {
// models.Enforcer在models/Casbin.go中定义并初始化
_ = models.Enforcer.AddPolicy(models.GetRoleString(role), fmt.Sprintf("%s%s", api, path), method)
}
}
}
最后别忘了初始化调用方法:
controllers/User.go
func init(){
registerUserPolicy()
}
Casbin
BeegoauthzCasbinauthzBasicAuthorizerEnforcer
filters/User.go(自己新建)
type BasicAuthorizer struct {
enforcer *casbin.Enforcer
}
SessionSession
filters/User.go
func (a *BasicAuthorizer) GetUserRole(input *context.BeegoInput) string {
user, ok := input.Session("user").(*models.User)
// 判断是否成功通过Session获取用户信息
if !ok || user.Role.Name == "" {
// 不成功的话直接返回匿名
return models.GetRoleString(models.RoleAnonymous)
}
return user.Role.Name
}
beego.FilterFuncCasbinEnforce布尔值
filters/User.go
func NewAuthorizer(e *casbin.Enforcer) beego.FilterFunc {
return func(ctx *context.Context) {
// 通过创建结构体,存放Enforcer
a := &BasicAuthorizer{enforcer: e}
// 获取用户角色
userRole := a.GetUserRole(ctx.Input)
// 获取访问路径
method := strings.ToLower(ctx.Request.Method)
// 获取访问方式
path := strings.ToLower(ctx.Request.URL.Path)
// 进行验证 - 失败则返回401
if status := a.enforcer.Enforce(userRole, path, method); !status {
ctx.Output.Status = 401
_ = ctx.Output.JSON(map[string]string{"msg": "用户权限不足"}, beego.BConfig.RunMode != "prod", false)
}
}
}
Casbin
最后我们还得把这个过滤器附加上:
main.go
func main() {
......
beego.InsertFilter("/v1/user/*", beego.BeforeRouter, filters.NewAuthorizer(models.Enforcer))
......
beego.Run()
}
9.总结
CasbinCasbin