Go写后台API
项目很渣,大佬可以提意见~~
技术栈
用的gin框架,登录验证使用jwt-go,权限管理是casbin,数据库使用mongodb
为什么要写这篇文章
因为想记录自己踩的坑,顺便记录心得。然后关于casbin这个库的使用资料很少,对于新手来说很难看明白,大神那肯定刷一遍就懂了。
正题
写到这里我们登录时差不多了。然后用postman测试一下
token已经拿到了,然后也返回了一些员工的信息。前端是采用的vue 直接就可以通过axios来获取。员工信息存到vuex里,然后通过getters就能获取了。
有了增加员工,和登录。差不多了撸权限吧。这里用casbin这个库,不得不说确实很强大。但是资料真的好少啊。琢磨了好几天。让我写这篇文章的目的也是因为这个库。 casbin采用的是PERM模型来设计的。一个模型conf至少有四个部分 [request_definition], [policy_definition], [policy_effect], [matchers]
如果模型使用RBAC,它也应该添加[role_definition]
,
请求定义
[request_definition]
是访问请求的定义。它定义了函数中的参数e.Enforce(...)
[request_definition]
r = sub, obj, act
sub, obj, act
,访问角色(Subject),访问的资源(Object) ,动作(Action)也可以叫访问的方法其实就是http方法GET POST那些
策略定义
其实就是定义了策略的规则。
[policy_definition]
p = sub, obj, act
p2 = sub, act
举个例子
p, alice, data1, read
p2, bob, write-all-objects
角色alice对path为data1有读的权限,角色bob可以对所有资源有wirte的权限。大概是这个意思。
策略效果
[policy_effect]
是策略效果的定义。它定义了在多个策略规则与请求匹配时是否应批准访问请求。例如,一个规则允许而另一个规则否认。
[policy_effect]
e = some(where (p.eft == allow))
p.eft它可以是allow或deny,它是可选的,默认是allow,这里我也没理解很透彻。所以没办法说得很清楚。大家可以研究研究。。。另一个例子
[policy_effect]
e = !some(where (p.eft == deny))
匹配器
[matchers]是策略匹配的定义。匹配器是表达式。它定义了如何根据请求评估策略规则。这里的语法就是正则表达式。
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
关于这个模型真的不是一两句能说完的。还是去官方看文档比我说来的快一点。。我们直接写项目来理解。
首先我们go get github.com/casbin/casbin
获取下这个包。根据说明我们要准备conf 文件。官方有个在线的editor,你可以根据选项生成conf ,因为我写的是restfulapi,所以我们采用restful的conf,把这个文件放到conf文件夹下面,给个名字auth_model.conf。
[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) && regexMatch(r.act, p.act)
官方是定义了一个policy.csv来放策略。在实际项目中,肯定是要持久化到数据库中的,官方有很多数据库和orm的adapter。我数据库是使用mongo所以采用mongodb-adapter。 持久化到数据库,我们指定我们自己的数据库,如果数据库中不存在casbin_rule这个表它会自己创建, 先定义一个权限的结构,然后我们通过Casbin
这个函数持久化到数据库并返回一个指针类型的enforcer
方便我们后面调用,添加权限通过AddCasbin
这个方法,把权限名,路径,方法名穿进去。
//权限结构
type CasbinModel struct {
ID bson.ObjectId `json:"id" bson:"_id"`
Ptype string `json:"ptype" bson:"ptype"`
RoleName string `json:"rolename" bson:"v0"`
Path string `json:"path" bson:"v1"`
Method string `json:"method" bson:"v2"`
}
//添加权限
func (c *CasbinModel) AddCasbin(cm CasbinModel) bool {
e := Casbin()
return e.AddPolicy(cm.RoleName, cm.Path, cm.Method)
}
//持久化到数据库
func Casbin() *casbin.Enforcer {
a := mongodbadapter.NewAdapter(mongoose.MongoUrl)
e := casbin.NewEnforcer("conf/auth_model.conf", a)
e.LoadPolicy()
return e
}
权限的model差不多了。接着写api
var (
casbins = mycasbin.CasbinModel{}
)
func AddCasbin(c *gin.Context) {
rolename := c.PostForm("rolename")
path := c.PostForm("path")
method := c.PostForm("method")
ptype := "p"
casbin := mycasbin.CasbinModel{
ID: bson.NewObjectId(),
Ptype: ptype,
RoleName: rolename,
Path: path,
Method: method,
}
isok := casbins.AddCasbin(casbin)
if isok {
c.JSON(http.StatusOK, gin.H{
"success": true,
"msg": "保存成功",
})
} else {
c.JSON(http.StatusOK, gin.H{
"success": false,
"msg": "保存失败",
})
}
}
接着写权限验证的中间件
//权限检查中间件
func AuthCheckRole() gin.HandlerFunc {
return func(c *gin.Context) {
//根据上下文获取载荷claims 从claims获得role
claims := c.MustGet("claims").(*jwt.Customclaims)
role := claims.Role
e := mycasbin.Casbin()
//检查权限
res, err := e.EnforceSafe(role, c.Request.URL.Path, c.Request.Method)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"status": -1,
"msg": "错误消息" + err.Error(),
})
c.Abort()
return
}
if res {
c.Next()
} else {
c.JSON(http.StatusOK, gin.H{
"status": 0,
"msg": "很抱歉您没有此权限",
})
c.Abort()
return
}
}
}
我们简单测试一下
看下结果。角色是manager
路径是/apis/addemp
方法是POST
,差不多了,有点满意了。看下数据库
casbin_rule这个表里有数据了,我之前测试已经添加了2个角色一个
admin
一个employee
。用员工的账号登录获取token然后到添加权限的路径下测试,从数据库看到employee
的角色是没有PATH/apis/addrole
的权限。如果看到失败那就是对的。
然后我们测试一下。
很满意了。这个结果是我预想的。后面我们还要动态的根据权限来循环生成路由。在把数据丢给前端vue处理,写的乱七八糟的。因为太困了...项目地址https://github.com/MartiniGo/leaseapp ,后续可能不会更新这个项目因为后面是业务逻辑准备放在生产环境。如果有什么新的东西新的坑我再更