文章首发于个人公众号:「阿拉平平」
前段时间试了下用 Gin 编写接口,但是在权限控制这块,网上很多资料都是用 casbin
来实现。网上关于 casbin 的文档良莠不齐,有的甚至看得我一脸懵逼。之后在 V 站上看到有大佬推荐了 Ladon
,于是我抽空折腾了下。
文中我将使用 Gin 和 Ladon 实现一个简易的 ACL 接口,通过向接口发送数据判断某用户是否具有操作资源的权限。
简介
Ladon[1] 是一个用于控制访问策略的库,类似于 RBAC 或 ACL,受 AWS IAM 策略启发,由 Golang 编写。
接口
由于 Ladon 本身没有提供 HTTP 服务,所以接口的需要自己实现。这里我用的是 Gin 框架。
首先进入项目目录 Ladon-demo
,安装 Gin:
$ cd Ladon-demo
$ go get -u github.com/gin-gonic/gin
创建 main.go
并编写接口,代码如下:
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
运行服务并测试接口返回值:
$ go run main.go
$ curl -s http://127.0.0.1:8080/ping
{"message":"pong"}
如果返回结果与我一致,说明接口服务正常。接下来就可以使用 Ladon 进行权限控制了。
概念
通常来说,一条完整的策略可以明确告诉我们:谁在特定环境下可以做什么。
因此,向接口发送的数据需要包含:
- subject:对应「谁」,即主体。
- action:对应「可以做」,即操作的种类,比如增删改查。
- resource:对应「做什么」,即具体操作的资源。
- context:对应「特定环境」,即一些限制条件,可选。
转换成 JSON 格式,大致如下:
{
"subject": "users:peter",
"action" : "delete",
"resource": "resources:articles:ladon-introduction",
"context": {
"remoteIP": "192.168.0.5"
}
}
后端存储的策略格式则复杂些,不过应该不难理解:
{
"description": "One policy to rule them all.",
"subjects": ["users:<peter|ken>", "users:maria", "groups:admins"],
"actions" : ["delete", "<create|update>"],
"effect": "allow",
"resources": [
"resources:articles:<.*>",
"resources:printer"
],
"conditions": {
"remoteIP": {
"type": "CIDRCondition",
"options": {
"cidr": "192.168.0.1/16"
}
}
}
}
实例
老板张三开了家理发店,并且雇佣了理发界三巨头 Tony、Kevin 和 Allen。现在需要为三位老师开通理发业务的权限。
安装导入
安装前确保 Go 版本在 1.11 以上。
$ go get github.com/ory/ladon
导入 Ladon:
import "github.com/ory/ladon"
创建策略
先创建一个策略:
var pol = &ladon.DefaultPolicy{
ID: "0",
Description: "Hair Design",
Subjects: []string{"<Tony|Kevin|Allen>"},
Resources: []string{
"resources:hair",
},
Actions: []string{"delete", "<create|update>"},
Effect: ladon.AllowAccess,
}
说明:
- ID:策略的标识。
- Description:策略的描述。
- Subjects:策略的主体。
<>
内为正则表达式。- Resources:策略的资源。
- Actions:策略的操作类型。
- Effect:AllowAccess 表示允许。DenyAccess 表示拒绝。
添加接口
现在添加一个接口 /check
,通过 POST 请求发送数据来验证刚刚创建的策略。
修改 main.go,加入以下代码:
r.POST("/check", func(c *gin.Context) {
// 声明数据结构体
accessRequest := &ladon.Request{}
var message string
// 绑定接口发送的数据
if err := c.BindJSON(accessRequest); err != nil {
fmt.Println(err)
} else {
// 实例化 warden
warden := &ladon.Ladon{
// 数据持久化
Manager: memory.NewMemoryManager(),
// 打印审计日志
AuditLogger: &ladon.AuditLoggerInfo{},
}
// 添加策略
warden.Manager.Create(pol)
// 判断是否拥有权限
if err := warden.IsAllowed(accessRequest); err != nil {
message = "无操作权限"
} else {
message = "有操作权限"
}
c.JSON(200, gin.H{
"message": message,
})
}
})
需要注意的是,在实例化 warden 时指定了用内存方式做数据持久化,因此需要导入 memory:
import "github.com/ory/ladon/manager/memory"
测试接口
访问 http://127.0.0.1:8080/check
测试接口。
先查看下 Tony 老师有没有权限:
$ curl -s \
> -X POST \
> -H "Content-Type: application/json" \
> -d@- \
> "http://127.0.0.1:8080/check" <<EOF
> {
> "subject": "Tony",
> "action" : "delete",
> "resource": "resources:hair"
> }
> EOF
{"message":"有操作权限"}
再测下张三有没有权限:
$ curl -s \
> -X POST \
> -H "Content-Type: application/json" \
> -d@- \
> "http://127.0.0.1:8080/check" <<EOF
> {
> "subject": "张三",
> "action" : "delete",
> "resource": "resources:hair"
> }
> EOF
{"message":"无操作权限"}
可以看到,测试结果符合我们的预期。张老板很开心,但是又提了个需求。
限定条件
张老板提出:现在叫 Tony 的人实在太多了,不能是个 Tony 就给他理发业务的权限。
张老板的需求不无道理,所以我们完善下之前的代码,给发送的数据加个限定条件,比如数据中需要指明 boss。
在 pol 里加入 Conditions:
var pol = &ladon.DefaultPolicy{
...
Conditions: ladon.Conditions{
"boss": &ladon.StringEqualCondition{
Equals: "张三",
},
},
}
这里的 StringEqualCondition
用于判断字符串是否相等,即发送的数据里 boss 是否等于 张三
。当然,Ladon 还自带了其它的限定条件,同时也支持自定义,这里就不展开了。
接下来测试下接口:
$ curl -s \
> -X POST \
> -H "Content-Type: application/json" \
> -d@- \
> "http://127.0.0.1:8080/check" <<EOF
> {
> "subject": "Tony",
> "action" : "delete",
> "resource": "resources:hair"
> }
> EOF
{"message":"无操作权限"}
这里没有指定 boss,所以 Tony 老师就没有权限了。现在指定下 boss 再测试下:
$ curl -s \
> -X POST \
> -H "Content-Type: application/json" \
> -d@- \
> "http://127.0.0.1:8080/check" <<EOF
> {
> "subject": "Tony",
> "action" : "delete",
> "resource": "resources:hair",
> "context": {
> "boss": "张三"
> }
> }
> EOF
{"message":"有操作权限"}
可以看到,在指定 boss 后,Tony 老师又获得了权限。
结语
有一说一,Ladon 相关的文档的确不多,功能也不如 casbin 强大。但是如果你熟悉 AWS IAM 策略的话,相信很快就能上手,在权限控制上也可以多一种选择。
References
[1] Ladon: https://github.com/ory/ladon