## Go写后台API
项目很渣,大佬可以提意见~~
### 技术栈
用的gin框架,登录验证使用jwt-go,权限管理是casbin,数据库使用mongodb
### 为什么要写这篇文章
因为想记录自己踩的坑,顺便记录心得。然后关于casbin这个库的使用资料很少,对于新手来说很难看明白,大神那肯定刷一遍就懂了。
### 正题
写到这里我们登录时差不多了。然后用postman测试一下
![01.png](https://static.studygolang.com/181104/05b025f000f75e61cd4fdb079e4d5094.png)
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`这个方法,把权限名,路径,方法名穿进去。
```go
//权限结构
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
```go
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": "保存失败",
})
}
}
```
接着写权限验证的中间件
```go
//权限检查中间件
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
}
}
}
```
我们简单测试一下![02.png](https://static.studygolang.com/181105/ed775eef7427a4ff6f9b552eb59238fb.png)
看下结果。角色是`manager` 路径是`/apis/addemp`方法是`POST`,差不多了,有点满意了。看下数据库![09.png](https://static.studygolang.com/181105/967d30d51cb6528f675dde40fb00e057.png)
casbin_rule这个表里有数据了,我之前测试已经添加了2个角色一个`admin`一个`employee`。用员工的账号登录获取token然后到添加权限的路径下测试,从数据库看到`employee`的角色是没有PATH`/apis/addrole`的权限。如果看到失败那就是对的。
然后我们测试一下。
![03.png](https://static.studygolang.com/181104/51bad599906e1ccd1e22870126e86dec.png)
很满意了。这个结果是我预想的。后面我们还要动态的根据权限来循环生成路由。在把数据丢给前端vue处理,写的乱七八糟的。因为太困了...项目地址https://github.com/MartiniGo/leaseapp ,后续可能不会更新这个项目因为后面是业务逻辑准备放在生产环境。如果有什么新的东西新的坑我再更
<a href="/user/zxing2004" title="@zxing2004">@zxing2004</a> 感觉很多都没说明白。。因为这个库的这个模型我也没理解透彻只是个分享
#5