[TOC]
- casbin实现基于角色的HTTP权限控制casbin介绍Casbin做了什么Casbin不做的事情配置示例模型与策略定制具体规则设置示例iris示例代码1.中间件格式 错误返回forbidden目录结构代码示例2.路由修饰模式 错误返回403目录结构代码示例提示
casbin实现基于角色的HTTP权限控制
casbin介绍
casbin是由北大的一位博士生主导开发的一个基于Go语言的权限控制库。支持 ACL,RBAC,ABAC 等常用的访问控制模型。
casbin是Golang项目的强大而高效的开源访问控制库。 它支持基于各种访问控制模型实施授权。
casbin的核心是一套基于PERM metamodel(Policy, Effect, Request, Matchers)的DSL。Casbin从用这种DSL定义的配置文件中读取访问控制模型,作为后续权限验证的基础
Casbin做了什么
- 支持自定义请求的格式,默认的请求格式为{subject, object, action}。
- 具有访问控制模型model和策略policy两个核心概念。
- 支持RBAC中的多层角色继承,不止主体可以有角色,资源也可以具有角色。
- 支持超级用户,如root或Administrator,超级用户可以不受授权策略的约束访问任意资源。
- 支持多种内置的操作符,如keyMatch,方便对路径式的资源进行管理,如/foo/bar可以映射到/foo*
Casbin不做的事情
- 身份认证authentication(即验证用户的用户名、密码),casbin只负责访问控制。应该有其他专门的组件负责身份认证,然后由casbin进行访问控制,二者是相互配合的关系。
- 管理用户列表或角色列表。Casbin认为由项目自身来管理用户、角色列表更为合适,用户通常有他们的密码,但是Casbin的设计思想并不是把它作为一个存储密码的容器。而是存储RBAC方案中用户和角色之间的映射关系。
配置示例
模型与策略定制
//sub "alice"// 想要访问资源的用户.//obj "data1" // 要访问的资源.//act "read" // 用户对资源执行的操作.# Request definition[request_definition]r = sub, obj, act# Policy definition[policy_definition]p = sub, obj, act# Policy effect[policy_effect]e = some(where (p.eft == allow))# Matchers[matchers]m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
可以看到这个配置文件主要定义了Request和Policy的组成结构.Policy effect和Matchers则灵活的多,可以包含一些自定义的表达式比如我们要加入一个名叫root的超级管理员,就可以这样写:
[matchers]m = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"
又比如我们可以用正则匹配来判断权限是否匹配:
[matchers]m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)
具体规则设置
p, alice, data1, readp, bob, data2, write
意思就是 alice 可以读 data1,bob 可以写 data2
示例
模型与策略定制 test.conf
[request_definition]r = sub, dom, obj, act[policy_definition]p = sub, dom, obj, act[role_definition]g = _, _, _[policy_effect]e = some(where (p.eft == allow))[matchers]m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
具体规则设置 test.csv
p, admin, domain1, data1, readp, admin, domain1, data1, writep, admin, domain2, data2, readp, admin, domain2, data2, writeg, alice, admin, domain1g, bob, admin, domain2
如上所示,alice 和 bob 分别是 domian1 和 domain2 的管理员
iris示例代码
1.中间件格式 错误返回forbidden
目录结构
主目录middleware
—— casbinmodel.conf —— casbinpolicy.csv —— main.go —— main_test.go
代码示例
casbinmodel.conf
[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 = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
casbinpolicy.csv
p, alice, /dataset1/*, GETp, alice, /dataset1/resource1, POSTp, bob, /dataset2/resource1, *p, bob, /dataset2/resource2, GETp, bob, /dataset2/folder1/*, POST
main.go
package mainimport ( "github.com/kataras/iris" "github.com/casbin/casbin" cm "github.com/iris-contrib/middleware/casbin")// $ go get github.com/casbin/casbin// $ go run main.go// Enforcer映射模型和casbin服务的策略,我们也在main_test上使用此变量。var Enforcer = casbin.NewEnforcer("casbinmodel.conf", "casbinpolicy.csv")func newApp() *iris.Application { casbinMiddleware := cm.New(Enforcer) app := iris.New() app.Use(casbinMiddleware.ServeHTTP) app.Get("/", hi) app.Get("/dataset1/{p:path}", hi) // p, alice, /dataset1/*, GET app.Post("/dataset1/resource1", hi) app.Get("/dataset2/resource2", hi) app.Post("/dataset2/folder1/{p:path}", hi) app.Any("/dataset2/resource1", hi) return app}func main() { app := newApp() app.Run(iris.Addr(":8080"))}func hi(ctx iris.Context) { ctx.Writef("Hello %s", cm.Username(ctx.Request()))}
main_test.go
package mainimport ( "testing" "github.com/iris-contrib/httpexpect" "github.com/kataras/iris/httptest")func TestCasbinMiddleware(t *testing.T) { app := newApp() e := httptest.New(t, app, httptest.Debug(false)) type ttcasbin struct { username string path string method string status int } tt := []ttcasbin{ {"alice", "/dataset1/resource1", "GET", 200}, {"alice", "/dataset1/resource1", "POST", 200}, {"alice", "/dataset1/resource2", "GET", 200}, {"alice", "/dataset1/resource2", "POST", 404}, {"bob", "/dataset2/resource1", "GET", 200}, {"bob", "/dataset2/resource1", "POST", 200}, {"bob", "/dataset2/resource1", "DELETE", 200}, {"bob", "/dataset2/resource2", "GET", 200}, {"bob", "/dataset2/resource2", "POST", 404}, {"bob", "/dataset2/resource2", "DELETE", 404}, {"bob", "/dataset2/folder1/item1", "GET", 404}, {"bob", "/dataset2/folder1/item1", "POST", 200}, {"bob", "/dataset2/folder1/item1", "DELETE", 404}, {"bob", "/dataset2/folder1/item2", "GET", 404}, {"bob", "/dataset2/folder1/item2", "POST", 200}, {"bob", "/dataset2/folder1/item2", "DELETE", 404}, } for _, tt := range tt { check(e, tt.method, tt.path, tt.username, tt.status) }}func check(e *httpexpect.Expect, method, path, username string, status int) { e.Request(method, path).WithBasicAuth(username, "password").Expect().Status(status)}
2.路由修饰模式 错误返回403
目录结构
主目录wrapper
—— casbinmodel.conf —— casbinpolicy.csv —— main.go —— main_test.go
代码示例
casbinmodel.conf
[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 = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
casbinpolicy.csv
p, alice, /dataset1/*, GETp, alice, /dataset1/resource1, POSTp, bob, /dataset2/resource1, *p, bob, /dataset2/resource2, GETp, bob, /dataset2/folder1/*, POSTp, cathrin, /dataset2/resource2, GETp, dataset1_admin, /dataset1/*, *g, cathrin, dataset1_admin
main.go
package mainimport ( "github.com/kataras/iris" "github.com/casbin/casbin" cm "github.com/iris-contrib/middleware/casbin")// $ go get github.com/casbin/casbin// $ go run main.go// Enforcer映射模型和casbin服务的策略,我们也在main_test上使用此变量。var Enforcer = casbin.NewEnforcer("casbinmodel.conf", "casbinpolicy.csv")func newApp() *iris.Application { casbinMiddleware := cm.New(Enforcer) app := iris.New() app.WrapRouter(casbinMiddleware.Wrapper()) app.Get("/", hi) app.Any("/dataset1/{p:path}", hi) // p, dataset1_admin, /dataset1/*, * && p, alice, /dataset1/*, GET app.Post("/dataset1/resource1", hi) app.Get("/dataset2/resource2", hi) app.Post("/dataset2/folder1/{p:path}", hi) app.Any("/dataset2/resource1", hi) return app}func main() { app := newApp() app.Run(iris.Addr(":8080"))}func hi(ctx iris.Context) { ctx.Writef("Hello %s", cm.Username(ctx.Request()))}
main_test.go
package mainimport ( "testing" "github.com/iris-contrib/httpexpect" "github.com/kataras/iris/httptest")func TestCasbinWrapper(t *testing.T) { app := newApp() e := httptest.New(t, app) type ttcasbin struct { username string path string method string status int } tt := []ttcasbin{ {"alice", "/dataset1/resource1", "GET", 200}, {"alice", "/dataset1/resource1", "POST", 200}, {"alice", "/dataset1/resource2", "GET", 200}, {"alice", "/dataset1/resource2", "POST", 403}, {"bob", "/dataset2/resource1", "GET", 200}, {"bob", "/dataset2/resource1", "POST", 200}, {"bob", "/dataset2/resource1", "DELETE", 200}, {"bob", "/dataset2/resource2", "GET", 200}, {"bob", "/dataset2/resource2", "POST", 403}, {"bob", "/dataset2/resource2", "DELETE", 403}, {"bob", "/dataset2/folder1/item1", "GET", 403}, {"bob", "/dataset2/folder1/item1", "POST", 200}, {"bob", "/dataset2/folder1/item1", "DELETE", 403}, {"bob", "/dataset2/folder1/item2", "GET", 403}, {"bob", "/dataset2/folder1/item2", "POST", 200}, {"bob", "/dataset2/folder1/item2", "DELETE", 403}, } for _, tt := range tt { check(e, tt.method, tt.path, tt.username, tt.status) } ttAdmin := []ttcasbin{ {"cathrin", "/dataset1/item", "GET", 200}, {"cathrin", "/dataset1/item", "POST", 200}, {"cathrin", "/dataset1/item", "DELETE", 200}, {"cathrin", "/dataset2/item", "GET", 403}, {"cathrin", "/dataset2/item", "POST", 403}, {"cathrin", "/dataset2/item", "DELETE", 403}, } for _, tt := range ttAdmin { check(e, tt.method, tt.path, tt.username, tt.status) } Enforcer.DeleteRolesForUser("cathrin") ttAdminDeleted := []ttcasbin{ {"cathrin", "/dataset1/item", "GET", 403}, {"cathrin", "/dataset1/item", "POST", 403}, {"cathrin", "/dataset1/item", "DELETE", 403}, {"cathrin", "/dataset2/item", "GET", 403}, {"cathrin", "/dataset2/item", "POST", 403}, {"cathrin", "/dataset2/item", "DELETE", 403}, } for _, tt := range ttAdminDeleted { check(e, tt.method, tt.path, tt.username, tt.status) }}func check(e *httpexpect.Expect, method, path, username string, status int) { e.Request(method, path).WithBasicAuth(username, "password").Expect().Status(status)}
提示
- 以上的go iris都是使用Basic Auth,用postman测试请选择Authorization选项
- .conf文件是配置规则模型,.csv是具体规则的体现,当然也可不使用这些东西,用户数据或者其他代替
- 解释一下我对这些的理解
//policy策略p, alice, /dataset1/*, GET //alice 用户有对 method为GET路径满足 /dataset1/*的访问权限 下面同理p, alice, /dataset1/resource1, POSTp, bob, /dataset2/resource1, *p, bob, /dataset2/resource2, GETp, bob, /dataset2/folder1/*, POSTp, cathrin, /dataset2/resource2, GETp, dataset1_admin, /dataset1/*, *g, cathrin, dataset1_admin //cathrin用户属于dataset1_admin组,也就是dataset1_admin能访问的cathrin都能访问,反之不然
//配置[request_definition] //请求定义r = sub, obj, act[policy_definition] //策略定义,也就是*.cvs文件 p 定义的格式p = sub, obj, act[role_definition] //组定义,也就是*.cvs文件 g 定义的格式g = _, _[policy_effect] e = some(where (p.eft == allow))[matchers] //满足条件m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")//请求用户与满足*.cvs p(策略)且满足g(组规则)且请求资源满足p(策略)规定资源
