集成go和casbin
source link: http://www.hi-roy.com/2020/09/28/集成gin和casbin/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
原文 ,省略了一些无关的内容。
如果你搜到这篇文章,那么什么是gin以及casbin应该不用过多解释了。
项目结构
root/ main.go # entry point of application handler/ # Gin handler functions middleware/ # Gin middlewares config/ # some configuration files like Casbin's rbac_model.conf component/ # global components like GORM DB instance
初始化数据库和缓存
在 component
目录下创建 persistence.go
用于初始化,这里使用 GORM
来处理数据库, BigCache
处理缓存:
import ( "fmt" "github.com/allegro/bigcache" _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" "time" ) var ( DB *gorm.DB GlobalCache *bigcache.BigCache ) func init() { // Connect to DB var err error DB, err = gorm.Open("mysql", "your_db_url") if err != nil { panic(fmt.Sprintf("failed to connect to DB: %v", err)) } // Initialize cache GlobalCache, err = bigcache.NewBigCache(bigcache.DefaultConfig(30 * time.Minute)) // Set expire time to 30 mins if err != nil { panic(fmt.Sprintf("failed to initialize cahce: %v", err)) } }
在这个示例中,我们使用数据库来存储casbin的polices,使用缓存存储登录用户信息。
配置Casbin
Model Configuration File
首先,你也许会发现casbin中有些概念让你很困惑,比如 Model Configuration File
。这里我不想讨论太多原理(因为我也不熟),直接举个例子,使用基于角色的权限控制(RBAC,Role-based access control)。所以首先在 config
目录创建 rbac_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
上面的文件定义了casbin如何判断用户拥有什么权限,例子中我们定义了5个字段:
-
r = sub, obj, act
定义了一个请求需要由3部分组成:sub=用户,obj=URL或资源,act=操作。 -
p = sub, obj, act
定义了策略的格式,比如admin,dada,write
表示admin有data的写权限。 -
e = some(where (p.eft == allow))
定义了用户可以做那些策略中定义准许他做的事。 -
g = _, _
定义了角色的格式,例如bob,admin
表示用户bob是admin这个角色。 -
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
定义了鉴权时的流程,先检查用户角色,再检查用户访问的资源,最后检查用户行为。
上面几个部分,仅1、2、3、5是必须的,如果不使用RBAC可以忽略4。
Roy注:下面的这个更常用。
[matchers]
m = g(r.sub, p.sub) == true \
&& keyMatch2(r.obj, p.obj) == true \
&& regexMatch(r.act, p.act) == true \
|| r.sub == “admin” \
|| keyMatch2(r.obj, “/auth”) == true
Polices
举个例子:
p, user, data, read p, admin, data, read p, admin, data, write g, Alice, admin g, Bob, user
首先我们定义了3个策略:
- user可以读取data
- admin可以写data
- admin可以读data
以及2个用户角色:
- Alice属于admin
- Bob属于user
所以Alice有数据的所有权限而Bob只能读取数据。官网教程中casbin使用csv来简单的存储策略,这里我们使用数据库。casbin通常把表名命名为 casbin_rule
,结构语句如下:
CREATE TABLE casbin_rule ( p_type VARCHAR(100), v0 VARCHAR(100), v1 VARCHAR(100), v2 VARCHAR(100) ); INSERT INTO casbin_rule VALUES('p', 'user', 'data', 'read'); INSERT INTO casbin_rule(p_type, v0, v1) VALUES('g', 'Bob', 'user');
实现Gin的Handler
首先实现登录逻辑
// handler/user_handler.go func Login(c *gin.Context) { username, password := c.PostForm("username"), c.PostForm("password") // Authentication // blahblah... // Generate random session id u, err := uuid.NewRandom() if err != nil { log.Fatal(err) } sessionId := fmt.Sprintf("%s-%s", u.String(), username) // Store current subject in cache component.GlobalCache.Set(sessionId, []byte(username)) // Send cache key back to client in cookie c.SetCookie("current_subject", sessionId, 30*60, "/resource", "", false, true) c.JSON(200, component.RestResponse{Code: 1, Message:username + " logged in successfully"}) }
如果登录成功,我们存储用户(或者叫sub)信息到缓存中,这里不要忘记将sessionId写回cookie中。casbin只负责鉴权不负责认证,所以我们要自己实现认证逻辑。接下来实现读、写逻辑:
// handler/resource_handler.go func ReadResource(c *gin.Context) { // some stuff // blahblah... c.JSON(200, component.RestResponse{Code: 1, Message: "read resource successfully", Data: "resource"}) } func WriteResource(c *gin.Context) { // some stuff // blahblah... c.JSON(200, component.RestResponse{Code: 1, Message: "write resource successfully", Data: "resource"}) }
然后实现 main.go
:
// main.go var ( router *gin.Engine ) func init() { // Initialize gin router router = gin.Default() corsConfig := cors.DefaultConfig() corsConfig.AllowAllOrigins = true corsConfig.AllowCredentials = true router.Use(cors.New(corsConfig)) // CORS configuraion router.POST("/user/login", handler.Login) router.GET("/resource", handler.ReadResource) router.POST("/resource", handler.WriteResource) } func main() { defer component.DB.Close() // Start our application err := router.Run(":8081") if err != nil { panic(fmt.Sprintf("failed to start gin engin: %v", err)) } log.Println("application is now running...") }
一切就绪,接下来开始集成。
启用casbin策略
从数据库加载polices
第一个问题就是,我们如何从数据库动态加载策略?我们可以使用 Casbin Adapters
,更精确的说我们使用的是 Gorm Adapter
。首先进行初始化:
// main.go func init() { // Initialize casbin adapter adapter, err := gormadapter.NewAdapterByDB(component.DB) if err != nil { panic(fmt.Sprintf("failed to initialize casbin adapter: %v", err)) } // Initialize gin router router = gin.Default() corsConfig := cors.DefaultConfig() corsConfig.AllowAllOrigins = true corsConfig.AllowCredentials = true router.Use(cors.New(corsConfig)) // CORS configuraion router.POST("/user/login", handler.Login) router.GET("/resource", handler.ReadResource) router.POST("/resource", handler.WriteResource) }
显然的,在进行任何操作前都需要经过鉴权,所以更优雅的方式是使用gin提供的 middlewares
和 grouping routes
:
// middleware/access_control.go // Authorize determines if current subject has been authorized to take an action on an object. func Authorize(obj string, act string, adapter *gormadapter.Adapter) gin.HandlerFunc { return func(c *gin.Context) { // Get current user/subject val, existed := c.Get("current_subject") if !existed { c.AbortWithStatusJSON(401, component.RestResponse{Message: "user hasn't logged in yet"}) return } // Casbin enforces policy ok, err := enforce(val.(string), obj, act, adapter) if err != nil { log.Println(err) c.AbortWithStatusJSON(500, component.RestResponse{Message: "error occurred when authorizing user"}) return } if !ok { c.AbortWithStatusJSON(403, component.RestResponse{Message: "forbidden"}) return } c.Next() } } func enforce(sub string, obj string, act string, adapter *gormadapter.Adapter) (bool, error) { // Load model configuration file and policy store adapter enforcer, err := casbin.NewEnforcer("config/rbac_model.conf", adapter) if err != nil { return false, fmt.Errorf("failed to create casbin enforcer: %w", err) } // Load policies from DB dynamically err = enforcer.LoadPolicy() if err != nil { return false, fmt.Errorf("failed to load policy from DB: %w", err) } // Verify ok, err := enforcer.Enforce(sub, obj, act) return ok, err }
最后进行一些修改:
// main.go func init() { // Initialize casbin adapter adapter, err := gormadapter.NewAdapterByDB(component.DB) if err != nil { panic(fmt.Sprintf("failed to initialize casbin adapter: %v", err)) } // Initialize Gin router router = gin.Default() corsConfig := cors.DefaultConfig() corsConfig.AllowAllOrigins = true corsConfig.AllowCredentials = true router.Use(cors.New(corsConfig)) // CORS configuraion router.POST("/user/login", handler.Login) // Secure our API resource := router.Group("/api") { resource.GET("/resource", middleware.Authorize("resource", "read", adapter), handler.ReadResource) resource.POST("/resource", middleware.Authorize("resource", "write", adapter), handler.WriteResource) } }
大功告成。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK