文章目录
- 0. 背景
- 1. 准备工作
- 2. 权限配置以及`增删改查`
- 2.1 策略和组使用规范
- 2.2 用户以及组关系的增删改查
- 2.2.1 获取所有用户以及关联的角色
- 2.2.2 角色组中添加用户
- 2.2.3 角色组中删除用户
- 2.3 角色组权限的`增删改查`
- 2.3.1 获取所有角色组权限
- 2.3.2 创建角色组权限
- 2.3.3 修改角色组权限
- 2.3.4 删除角色组权限
- 3. 测试以及完整代码
- 3.1 casbin_service.go
- 3.2 casbin_service_test.go
- 3.3测试结果
- 4. 结语
0. 背景
Casbin是用于Golang项目的功能强大且高效的开源访问控制库。
强大通用也意味着概念和配置较多,具体到实际应用(以Gin Web框架开发)需要解决以下问题:
- 权限配置的存储,以及
增删改查
- Gin框架的中间件如何实现
经过一番摸索实践出经验,计划分为三个章节,循序渐进的介绍使用方法
- Casbin概念介绍以及库使用
- 使用Gorm存储Casbin权限配置以及
增删改查
- 实现Gin鉴权中间件
1. 准备工作
接上一章,略改进一下,将
model.conf
文件内容存储到go字符串中,最终代码如下:
package main
import (
"log"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormadapter "github.com/casbin/gorm-adapter/v3"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
a, err := gormadapter.NewAdapterByDB(db)
if err != nil {
panic("new gorm adapter error: " + err.Error())
}
m, err := model.NewModelFromString(`[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 && keyMatch2(r.obj,p.obj) && r.act == p.act`)
if err != nil {
panic("new model error: " + err.Error())
}
e, err := casbin.NewEnforcer(m, a)
if err != nil {
panic("new casbin enforcer error: " + err.Error())
}
e.LoadPolicy()
// 添加策略
ok, err := e.AddPolicy("admin", "/api/user", "GET")
log.Println("add admin /api/user GET: ", ok, err)
ok, err = e.AddGroupingPolicy("leo", "admin")
log.Println("add leo to admin group: ", ok, err)
e.SavePolicy()
ok, err = e.Enforce("leo", "/api/user", "GET")
log.Println("leo GET /api/user :", ok, err)
ok, err = e.Enforce("leo", "/api/user", "DELETE")
log.Println("leo DELETE /api/user :", ok, err)
}
上述代码默认使用的
gormadapter.CasbinRule
对应的Go结构和数据库表如下
type CasbinRule struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Ptype string `gorm:"size:100"`
V0 string `gorm:"size:100"`
V1 string `gorm:"size:100"`
V2 string `gorm:"size:100"`
V3 string `gorm:"size:100"`
V4 string `gorm:"size:100"`
V5 string `gorm:"size:100"`
}
2. 权限配置以及增删改查
以上准备知识,仅了解和casbin基本操作以及如何配合gorm存储到DB中,还需要完善权限,以及提供
增删改查
操作
2.1 策略和组使用规范
casbin的policy十分灵活,具体到自己业务使用中,我这里按以下两条规则使用,基本能满足业务需求
- 所有策略只针对角色组设置
- 用户关联到组(一个用户可以有多个组)
如下,两个角色组
admin
和user
组,admin组
能查询
和删除
用户,user组
只能查询用户
ptype | v0 | v1 | v2 | v3 | v4 | v5 |
---|---|---|---|---|---|---|
p | admin | /api/user | GET | |||
p | admin | /api/user | DELETE | |||
p | user | /api/user | GET | |||
… | … | … | ||||
g | leo | admin | ||||
g | leo2 | user |
基础代码如下,下一节给
CasbinService
添加增删改查
功能
package main
import (
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormadapter "github.com/casbin/gorm-adapter/v3"
"gorm.io/gorm"
)
type CasbinService struct {
enforcer *casbin.Enforcer
adapter *gormadapter.Adapter
}
func NewCasbinService(db *gorm.DB) (*CasbinService, error) {
a, err := gormadapter.NewAdapterByDB(db)
if err != nil {
return nil, err
}
m, err := model.NewModelFromString(`[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 && keyMatch2(r.obj,p.obj) && r.act == p.act`)
if err != nil {
return nil, err
}
e, err := casbin.NewEnforcer(m, a)
if err != nil {
return nil, err
}
return &CasbinService{adapter: a, enforcer: e}, nil
}
2.2 用户以及组关系的增删改查
2.2.1 获取所有用户以及关联的角色
根据
2.1
中示范数据,预期输出
[{
"username": "leo",
"roleNames": ["admin"]
}, {
"username": "leo2",
"roleNames": ["user"]
}]
获取所有用户以及关联的角色代码片段
type User struct {
UserName string
RoleNames []string
}
// 获取所有用户以及关联的角色
func (c *CasbinService) GetUsers() (users []User) {
p := c.enforcer.GetGroupingPolicy()
usernameUser := make(map[string]*User, 0)
for _, _p := range p {
username, usergroup := _p[0], _p[1]
if v, ok := usernameUser[username]; ok {
usernameUser[username].RoleNames = append(v.RoleNames, usergroup)
} else {
usernameUser[username] = &User{UserName: username, RoleNames: []string{usergroup}}
}
}
for _, v := range usernameUser {
users = append(users, *v)
}
return
}
2.2.2 角色组中添加用户
// 角色组中添加用户, 没有组默认创建
func (c *CasbinService) UpdateUserRole(username, rolename string) error {
_, err := c.enforcer.AddGroupingPolicy(username, rolename)
return err
}
2.2.3 角色组中删除用户
// 角色组中删除用户
func (c *CasbinService) DeleteUserRole(username, rolename string) error {
_, err := c.enforcer.RemoveGroupingPolicy(username, rolename)
return err
}
2.3 角色组权限的增删改查
2.3.1 获取所有角色组权限
根据
2.1
中示范数据,预期输出形式
[{
"roleName": "admin",
"url": "/api/user",
"method": "DELETE"
}, {
"roleName": "admin",
"url": "/api/user",
"method": "GET"
}, {
"roleName": "user",
"url": "/api/user",
"method": "GET"
}]
获取所有角色组权限
```go
// (RoleName, Url, Method) 对应于 `CasbinRule` 表中的 (v0, v1, v2)
type RolePolicy struct {
RoleName string `gorm:"column:v0"`
Url string `gorm:"column:v1"`
Method string `gorm:"column:v2"`
}
// 获取所有角色组权限
func (c *CasbinService) GetRolePolicy() (roles []RolePolicy, err error) {
err = c.adapter.GetDb().Model(&gormadapter.CasbinRule{}).Where("ptype = 'p'").Find(&roles).Error
if err != nil {
return nil, err
}
return
}
2.3.2 创建角色组权限
// 创建角色组权限, 已有的会忽略
func (c *CasbinService) CreateRolePolicy(r RolePolicy) error {
// 不直接操作数据库,利用enforcer简化操作
err := c.enforcer.LoadPolicy()
if err != nil {
return err
}
_, err = c.enforcer.AddPolicy(r.RoleName, r.Url, r.Method)
if err != nil {
return err
}
return c.enforcer.SavePolicy()
}
2.3.3 修改角色组权限
// 修改角色组权限
func (c *CasbinService) UpdateRolePolicy(old, new RolePolicy) error {
_, err := c.enforcer.UpdatePolicy([]string{old.RoleName, old.Url, old.Method},
[]string{new.RoleName, new.Url, new.Method})
if err != nil {
return err
}
return c.enforcer.SavePolicy()
}
2.3.4 删除角色组权限
// 删除角色组权限
func (c *CasbinService) DeleteRolePolicy(r RolePolicy) error {
_, err := c.enforcer.RemovePolicy(r.RoleName, r.Url, r.Method)
if err != nil {
return err
}
return c.enforcer.SavePolicy()
}
3. 测试以及完整代码
3.1 casbin_service.go
package main
import (
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormadapter "github.com/casbin/gorm-adapter/v3"
"gorm.io/gorm"
)
/*
按如下约定:
1. 所有策略只针对角色组设置
2. 用户关联到组(一个用户可以有多个组)
+-------+-------+-----------+--------+----+----+----+
| ptype | v0 | v1 | v2 | v3 | v4 | v5 |
+-------+-------+-----------+--------+----+----+----+
| p | admin | /api/user | GET | | | |
+-------+-------+-----------+--------+----+----+----+
| p | admin | /api/user | DELETE | | | |
+-------+-------+-----------+--------+----+----+----+
| p | user | /api/user | GET | | | |
+-------+-------+-----------+--------+----+----+----+
| ... | ... | ... | | | | |
+-------+-------+-----------+--------+----+----+----+
| g | leo | admin | | | | |
+-------+-------+-----------+--------+----+----+----+
| g | leo2 | admin | | | | |
+-------+-------+-----------+--------+----+----+----+
*/
type CasbinService struct {
enforcer *casbin.Enforcer
adapter *gormadapter.Adapter
}
func NewCasbinService(db *gorm.DB) (*CasbinService, error) {
a, err := gormadapter.NewAdapterByDB(db)
if err != nil {
return nil, err
}
m, err := model.NewModelFromString(`[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 && keyMatch2(r.obj,p.obj) && r.act == p.act`)
if err != nil {
return nil, err
}
e, err := casbin.NewEnforcer(m, a)
if err != nil {
return nil, err
}
return &CasbinService{adapter: a, enforcer: e}, nil
}
// (RoleName, Url, Method) 对应于 `CasbinRule` 表中的 (v0, v1, v2)
type RolePolicy struct {
RoleName string `gorm:"column:v0"`
Url string `gorm:"column:v1"`
Method string `gorm:"column:v2"`
}
// 获取所有角色组权限
func (c *CasbinService) GetRolePolicy() (roles []RolePolicy, err error) {
err = c.adapter.GetDb().Model(&gormadapter.CasbinRule{}).Where("ptype = 'p'").Find(&roles).Error
if err != nil {
return nil, err
}
return
}
// 创建角色组权限, 已有的会忽略
func (c *CasbinService) CreateRolePolicy(r RolePolicy) error {
// 不直接操作数据库,利用enforcer简化操作
err := c.enforcer.LoadPolicy()
if err != nil {
return err
}
_, err = c.enforcer.AddPolicy(r.RoleName, r.Url, r.Method)
if err != nil {
return err
}
return c.enforcer.SavePolicy()
}
// 修改角色组权限
func (c *CasbinService) UpdateRolePolicy(old, new RolePolicy) error {
_, err := c.enforcer.UpdatePolicy([]string{old.RoleName, old.Url, old.Method},
[]string{new.RoleName, new.Url, new.Method})
if err != nil {
return err
}
return c.enforcer.SavePolicy()
}
// 删除角色组权限
func (c *CasbinService) DeleteRolePolicy(r RolePolicy) error {
_, err := c.enforcer.RemovePolicy(r.RoleName, r.Url, r.Method)
if err != nil {
return err
}
return c.enforcer.SavePolicy()
}
type User struct {
UserName string
RoleNames []string
}
// 获取所有用户以及关联的角色
func (c *CasbinService) GetUsers() (users []User) {
p := c.enforcer.GetGroupingPolicy()
usernameUser := make(map[string]*User, 0)
for _, _p := range p {
username, usergroup := _p[0], _p[1]
if v, ok := usernameUser[username]; ok {
usernameUser[username].RoleNames = append(v.RoleNames, usergroup)
} else {
usernameUser[username] = &User{UserName: username, RoleNames: []string{usergroup}}
}
}
for _, v := range usernameUser {
users = append(users, *v)
}
return
}
// 角色组中添加用户, 没有组默认创建
func (c *CasbinService) UpdateUserRole(username, rolename string) error {
_, err := c.enforcer.AddGroupingPolicy(username, rolename)
return err
}
// 角色组中删除用户
func (c *CasbinService) DeleteUserRole(username, rolename string) error {
_, err := c.enforcer.RemoveGroupingPolicy(username, rolename)
return err
}
3.2 casbin_service_test.go
package main
import (
"testing"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
func TestCasbinService(t *testing.T) {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
s, err := NewCasbinService(db)
if err != nil {
t.Fatalf("new service error: %v", err)
}
// 创建个用户,分别关联到`admin`和`user`组, `leo2`既是`admin`组又是`user`组
t.Logf("create user: leo with group: admin result: %v", s.UpdateUserRole("leo", "admin"))
t.Logf("create user: leo with group: admin result: %v", s.UpdateUserRole("leo2", "admin"))
t.Logf("create user: leo with group: admin result: %v", s.UpdateUserRole("leo2", "user"))
t.Log()
t.Logf("users is: %v\n", s.GetUsers())
// 针对`admin`和`user`组创建三条策略
t.Log("create admin /api/user GET: ", s.CreateRolePolicy(RolePolicy{RoleName: "admin", Url: "/api/user", Method: "GET"}))
t.Log("create admin /api/user DELETE: ", s.CreateRolePolicy(RolePolicy{RoleName: "admin", Url: "/api/user", Method: "DELETE"}))
t.Log("create user /api/user GET: ", s.CreateRolePolicy(RolePolicy{RoleName: "user", Url: "/api/user", Method: "GET"}))
t.Log("all policy is: ")
t.Log(s.GetRolePolicy())
t.Log("delete admin /api/user GET", s.DeleteRolePolicy(RolePolicy{RoleName: "admin", Url: "/api/user", Method: "GET"}))
}
3.3测试结果
4. 结语
基本的权限模型设计以及操作函数均已设计正常, 下一章开始结合
gin
框架设计一个中间件, 实现casbin权限验证