golang iris 学习三: casbin权限验证

Casbin官方教程

撸个简单的权限系统:用户包含角色, 角色包含权限

先定义一份简单的权限规则库

相当于定义菜单权限

casbin.conf

1
2
3
4
5
6
7
8
9
10
11
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && (p.act == "*" || regexMatch(r.act, p.act))
  • request_definition: 可以理解为验证函数传进去的参数
  • policy_definition: 可以理解为添加规则函数传进去的参数,就是存进数据库的数据规则
  • policy_effect: 对结果进行判定
  • matchers: 匹配规则

先测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func main() {
enforcer := datasource.GetEnforce()

// 添加规则
ok, err := enforcer.AddPolicy("测试", "test.a1", "test", "/test/a1", "get")
if err != nil {
fmt.Println(err)
}
fmt.Printf("添加规则:%v\n", ok)

// 测试规则
ok, err = enforcer.Enforce("test.a1", "/test/a1", "get")
if err != nil {
fmt.Println(err)
}
fmt.Printf("规则测试:%v\n", ok)

ok, err = enforcer.Enforce("test.a1", "/test/a111", "get")
if err != nil {
fmt.Println(err)
}
fmt.Printf("规则测试:%v\n", ok)
}

---
添加规则:true
规则测试:true
规则测试:false

增加组的权限

相当于角色权限 和 用户权限

casbin.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[role_definition]
g = _, _

[matchers]
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (p.act == "*" || regexMatch(r.act, p.act))
  • role_definition: 分组功能

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package main

import (
"fmt"
"goms/datasource"
)

func main() {
enforcer := datasource.GetEnforce()

// 添加规则
ok, err := enforcer.AddPolicy("test.a1", "/test/a1", "get")
if err != nil {
fmt.Println(err)
}
fmt.Printf("添加规则:%v\n", ok)

// 测试规则
ok, err = enforcer.Enforce("test.a1", "/test/a1", "get")
if err != nil {
fmt.Println(err)
}
fmt.Printf("规则测试:%v\n", ok)

ok, err = enforcer.Enforce("test.a1", "/test/a111", "get")
if err != nil {
fmt.Println(err)
}
fmt.Printf("规则测试:%v\n", ok)

// 添加角色
ok, _ = enforcer.AddGroupingPolicy("yy", "test.a1")
fmt.Printf("添加角色:%v\n", ok)

// 测试角色
ok, _ = enforcer.Enforce("yy", "/test/a1", "get")
fmt.Printf("规则测试:%v\n", ok)

ok, _ = enforcer.Enforce("yy", "/test/a111", "get")
fmt.Printf("规则测试:%v\n", ok)

// 添加用户
ok, _ = enforcer.AddGroupingPolicy("wisp", "yy")
fmt.Printf("添加角色:%v\n", ok)

// 测试用户
ok, _ = enforcer.Enforce("wisp", "/test/a1", "get")
fmt.Printf("规则测试:%v\n", ok)

ok, _ = enforcer.Enforce("wisp", "/test/a111", "get")
fmt.Printf("规则测试:%v\n", ok)

// 查询
ret := enforcer.GetFilteredPolicy(0, "test.a1")
fmt.Println(ret)

ret = enforcer.GetFilteredGroupingPolicy(0, "yy")
fmt.Println(ret)

ret = enforcer.GetFilteredGroupingPolicy(0, "wisp")
fmt.Println(ret)
}
---
添加规则:false
规则测试:true
规则测试:false
添加角色:false
规则测试:true
规则测试:false
添加角色:true
规则测试:true
规则测试:false
[[test.a1 /test/a1 get]]
[[yy test.a1]]
[[wisp yy]]

很简单,wisp拥有yy角色,yy角色包含test.a1 /test/a1 get 权限

怎么结合到iris里面去呢,想来想去也想不懂,总不能把用户系统写到casbin里面来吧,要不就添加用户权限的时候更新一下casbin数据库,要不就登陆或者刷新的时候把用户权限写进casbin数据库,感觉麻,还是后面这种方便一点

先增加一份初始化代码

datasource/casbin.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package datasource

import (
"fmt"
"github.com/casbin/casbin/v2"
mongodbadapter "github.com/casbin/mongodb-adapter/v2"
"goms/config"
)

var Enforcer *casbin.Enforcer

func init() {
mongoInfo := config.GConfig.Mongo
mongoUrl := fmt.Sprintf("mongodb://%s:%d/%s", mongoInfo.Host, mongoInfo.Port, mongoInfo.Name)
a := mongodbadapter.NewAdapter(mongoUrl)

e, err := casbin.NewEnforcer("casbin.conf", a)
if err != nil {
panic(err)
}

e.LoadPolicy()
e.EnableAutoSave(true)
Enforcer = e
}

func GetEnforce() *casbin.Enforcer {
return Enforcer
}

增加casbin规则的增加、删除方法

repo/login_repo.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package repo

import (
"fmt"
"goms/datamodels"
"goms/datasource"
"gopkg.in/mgo.v2/bson"
)

type LoginRepository interface {
Check(query bson.M) (user datamodels.User, err error)
RemovePolicy(userID string) (err error)
LoadPolicy(userID string, menu datamodels.Menu) (err error)
}

type loginRepository struct {
collection string
}

func NewLoginRepository() LoginRepository {
return &loginRepository{collection: "project_user"}
}

func (l *loginRepository) Check(query bson.M) (user datamodels.User, err error) {
db := datasource.NewSessionStore()
defer db.Close()
col := db.C(l.collection)

err = col.Find(query).One(&user)
if err != nil {
return
}
if !user.IsActive {
err = fmt.Errorf("该用户已被禁用!")
return
}
return
}

func (l *loginRepository) RemovePolicy(userID string) (err error) {
enforcer := datasource.GetEnforce()
_, err = enforcer.RemoveFilteredPolicy(0, userID)
return
}

func (l *loginRepository) LoadPolicy(userID string, menu datamodels.Menu) (err error) {
enforcer := datasource.GetEnforce()
for _, permission := range menu.Permission {
if permission.Url == "" {
continue
}
_, err = enforcer.AddPolicy(userID, permission.Url, permission.Method)
}
return
}

主要就是利用casbin的两个API:

  • enforcer.RemoveFilteredPolicy
  • enforcer.AddPolicy

用户刷新的时候更新一次casbin权限

说白了当这个东西是个缓存来用

services/login_service.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func (l *loginService) Info(tokenString string) (response datamodels.Response) {
claims, _ := middleware.ParseToken(tokenString)
username := claims["username"].(string)
user, err := l.repo.Check(bson.M{"username": username})
if err != nil {
response.Code = config.DBSelectErr
response.Msg = fmt.Sprintf("查询数据失败:%v", err)
return
}
if user.IsAdmin {
user.Roles = []string{"admin"}
} else {
menuCodes := make([]string, 5)
l.repo.RemovePolicy(user.ID.Hex()) // 删除该用户全部权限
for _, id := range user.Roles {
role, _ := l.roleRepo.GetByID(id)
for _, menuID := range role.Menus {
menu, _ := l.menuRepo.GetByID(menuID)
l.repo.LoadPolicy(user.ID.Hex(), menu) // 重新添加用户权限
menuCodes = append(menuCodes, menu.Code)
}
}
user.Roles = menuCodes
}
response.Code = config.StatusOk
response.Msg = "success"
response.Data = user
return
}

用户登陆或者刷新后数据库保存的规则如下:

20191206093921

增加中间件验证权限:

middleware/casbin_middleware.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package middleware

import (
"github.com/dgrijalva/jwt-go"
"github.com/kataras/iris/v12"
"goms/config"
"goms/datamodels"
"goms/datasource"
)

func CasbinHandler(ctx iris.Context) {
token := ctx.Values().Get("jwt").(*jwt.Token)
claims := token.Claims.(jwt.MapClaims)
userID := claims["userId"]
isAdmin := claims["isAdmin"].(bool)
url := ctx.Path()
method := ctx.Method()

if !isAdmin {
enforcer := datasource.GetEnforce()
enforcer.LoadPolicy()
ok, _ := enforcer.Enforce(userID, url, method)
if !ok {
ctx.StopExecution()
response := &datamodels.Response{
Code: config.AccessDenied,
Msg: "没有权限!",
}
ctx.JSON(response)
}
}
ctx.Next()
}

把中间件添加进路由

route/route.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package route

import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"goms/controllers"
"goms/middleware"
)

func InitRoute(app *iris.Application) {
mvc.Configure(app.Party("/account"), func(m *mvc.Application) {
m.Handle(controllers.NewLoginController())
})

app.Use(middleware.JwtHandler().Serve)
app.Use(middleware.CasbinHandler)

bathPath := "/api/v1"
mvc.Configure(app.Party(bathPath+"/games"), func(m *mvc.Application) {
m.Handle(controllers.NewGameController())
})

}

大功告成~