RBAC (Role Based Access Control, 基于角色的访问控制)
准备基础的 Gin 项目
新建 gin-rbac 文件夹
mkdir gin-rbac
进入该目录, 初始化项目, 获取依赖
go mod init gin-rbac
go get -u github.com/gin-gonic/gin
go get -u github.com/casbin/casbin/v2
go get -u github.com/gin-contrib/authz
准备 Casbin
在 gin-rbac 目录下新建 auth 目录, 在该目录下
新建 model.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) && r.obj == p.obj && r.act == p.act
新建 policy.csv:
p, admin, data1, read
p, admin, data1, write
p, data_entry, data1, write
p, guest, data1, read
g, alice, admin
g, bob, data_entry
g, charlie, guest
在 gin-rbac 目录下新建 casbin_test.go 并测试:
package main
import (
"testing"
"github.com/casbin/casbin/v2"
)
func TestEnforce(t *testing.T) {
e, _ := casbin.NewEnforcer("auth/model.conf", "auth/policy.csv")
sub := "alice"
obj := "data1"
act := "read"
if res, _ := e.Enforce(sub, obj, act); res {
// 允许 alice read data1
t.Log("alice can read data1")
} else {
// 拒绝请求
t.Log("alice can NOT read data1")
}
// alice can read data1
sub = "bob"
obj = "data1"
act = "read"
if res, _ := e.Enforce(sub, obj, act); res {
t.Log("bob can read data1")
} else {
t.Log("bob can NOT read data1")
}
// bob can NOT read data1
sub = "bob"
obj = "data1"
act = "write"
if res, _ := e.Enforce(sub, obj, act); res {
t.Log("bob can write data1")
} else {
t.Log("bob can NOT write data1")
}
// bob can write data1
}
中间件 gin-contrib/authz
其 github 主页 给出的例子
package main
import (
"net/http"
"github.com/casbin/casbin/v2"
"github.com/gin-contrib/authz"
"github.com/gin-gonic/gin"
)
func main() {
e := casbin.NewEnforcer("authz_model.conf", "authz_policy.csv")
router := gin.New()
router.Use(authz.NewAuthorizer(e))
}
一旦认证失败就返回 HTTP 403.
查看源码 https://github.com/gin-contrib/authz/blob/master/authz.go
得知其用到了 Basic 认证, 其中关键代码:
func (a *BasicAuthorizer) CheckPermission(r *http.Request) bool {
user := a.GetUserName(r) // 从 Basic 认证中拿到用户名
method := r.Method // http 请求方法, 例如 GET / POST
path := r.URL.Path // 请求路径, 例如 /data1/read /data1/write
allowed, err := a.enforcer.Enforce(user, path, method)
if err != nil {
panic(err)
}
return allowed
}
整合 Casbin 和 Gin
新建 auth/authz_model.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) && r.obj == p.obj && r.act == p.act
新建 auth/authz_policy.csv:
p, admin, /data1/read, GET
p, admin, /data1/write, POST
p, data_entry, /data1/write, POST
p, guest, /data1/read, GET
g, alice, admin
g, bob, data_entry
g, charlie, guest
新建 gin-rbac/main.go:
package main
import (
"github.com/casbin/casbin/v2"
"github.com/gin-contrib/authz"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
enforcer, err := casbin.NewEnforcer("auth/authz_model.conf", "auth/authz_policy.csv")
if err != nil {
panic(err)
}
r.Use(authz.NewAuthorizer(enforcer))
r.GET("/data1/read", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "You can read data1"})
})
r.POST("/data1/write", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "You can write data1"})
})
r.Run()
}
使用 httpie 发送请求:
-
alice 有读和写权限, 因为她的角色是
admin:http -v -a alice:alice GET 'localhost:8080/data1/read'GET /data1/read HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Authorization: Basic YWxpY2U6YWxpY2U= Connection: keep-alive Host: localhost:8080 User-Agent: HTTPie/2.6.0 HTTP/1.1 200 OK Content-Length: 32 Content-Type: application/json; charset=utf-8 Date: Mon, 09 Oct 2023 09:04:41 GMT { "message": "You can read data1" } -
someone 是匿名用户, 他什么权限都没有
http -v -a someone:xx GET 'localhost:8080/data1/read'GET /data1/read HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Authorization: Basic c29tZW9uZTp4eA== Connection: keep-alive Host: localhost:8080 User-Agent: HTTPie/2.6.0 HTTP/1.1 403 Forbidden Content-Length: 0 Date: Mon, 09 Oct 2023 09:05:52 GMT -
bob 只有写权限, 他的角色是
data_entryhttp -v -a bob:bob GET 'localhost:8080/data1/read'GET /data1/read HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Authorization: Basic Ym9iOmJvYg== Connection: keep-alive Host: localhost:8080 User-Agent: HTTPie/2.6.0 HTTP/1.1 403 Forbidden Content-Length: 0 Date: Mon, 09 Oct 2023 09:06:32 GMThttp -v -a bob:bob POST 'localhost:8080/data1/write'Accept: */* Accept-Encoding: gzip, deflate Authorization: Basic Ym9iOmJvYg== Connection: keep-alive Content-Length: 0 Host: localhost:8080 User-Agent: HTTPie/2.6.0 HTTP/1.1 200 OK Content-Length: 33 Content-Type: application/json; charset=utf-8 Date: Mon, 09 Oct 2023 09:07:21 GMT { "message": "You can write data1" }
参考
- https://gitee.com/pyq19/gin-rbac
- https://medium.com/@abhishekranjandev/creating-a-spring-security-like-rbac-system-for-your-gin-project-using-casbin-41364211ae67
- https://dev.to/maxwellhertz/tutorial-integrate-gin-with-cabsin-56m0
- https://github.com/casbin/casbin
- https://casbin.org/docs/tutorials/
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
- https://github.com/gin-contrib/authz