主页 > IT业界  > 

GoGinGormCasbin权限管理实现-3.实现Gin鉴权中间件

GoGinGormCasbin权限管理实现-3.实现Gin鉴权中间件

文章目录 0. 背景1. 准备工作2. gin中间件2.1 中间件代码2.2 中间件使用2.3 测试中间件使用结果 3. 添加权限管理API3.1 获取所有用户3.2 获取所有角色组3.3 获取所有角色组的策略3.4 修改角色组策略3.5 删除角色组策略3.6 添加用户到组3.7 从组中删除用户3.8 测试API 4. 最终目录结构和代码4.1 main.go4.2 casbin.go4.3 middleware.go 5. 更进一步

0. 背景

Casbin是用于Golang项目的功能强大且高效的开源访问控制库。 强大通用也意味着概念和配置较多,具体到实际应用(以Gin Web框架开发)需要解决以下问题:

权限配置的存储,以及增删改查Gin框架的中间件如何实现

经过一番摸索实践出经验,计划分为三个章节,循序渐进的介绍使用方法 1. Casbin概念介绍以及库使用 2. 使用Gorm存储Casbin权限配置以及增删改查 3.实现Gin鉴权中间件

代码地址 gitee /leobest2/gin-casbin-example

1. 准备工作

上一章已实现了casbin和gorm权限模型设计以及增删改查操作,本章在此基础上,实现以下需求

集成到gin中,添加一个鉴权中间件提供角色,用户增删改查API接口:

至此当前目录结构

. ├── casbin.go ├── go.mod ├── go.sum └── test.db

casbin.go完整代码见上一章结尾:

2. gin中间件 2.1 中间件代码

添加一个middleware.go, 这里简便起见,假设用户从url传递 /xxxx?username=leo,实际应用中可以结合jwt等鉴权

HTTP GET /api/user?username=leo

package main import "github /gin-gonic/gin" func NewCasbinAuth(srv *CasbinService) gin.HandlerFunc { return func(ctx *gin.Context) { err := srv.enforcer.LoadPolicy() if err != nil { ctx.String(500, err.Error()) ctx.Abort() return } // 简便起见,假设用户从url传递 /xxxx?username=leo,实际应用可以结合jwt等鉴权 username, _ := ctx.GetQuery("username") ok, err := srv.enforcer.Enforce(username, ctx.Request.URL.Path, ctx.Request.Method) if err != nil { ctx.String(500, err.Error()) ctx.Abort() return } else if !ok { ctx.String(403, "验证权限失败!") ctx.Abort() return } ctx.Next() } } 2.2 中间件使用

main.go

package main import ( "github /gin-gonic/gin" "github /glebarez/sqlite" "gorm.io/gorm" ) func main() { db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) if err != nil { panic("failed to connect database: " + err.Error()) } casbinService, err := NewCasbinService(db) if err != nil { panic("failed to new casbin service: " + err.Error()) } r := gin.Default() auth := r.Group("/api") auth.Use(NewCasbinAuth(casbinService)) auth.GET("/api/user", func(ctx *gin.Context) { ctx.String(200, "get /api/user success") }) auth.DELETE("/api/user", func(ctx *gin.Context) { ctx.String(200, "delete /api/user success") }) r.Run(":8000") } 2.3 测试中间件使用结果

测试权限数据库内容

ptypev0v1v2v3v4v5padmin/api/userGETpadmin/api/userDELETEpuser/api/userGETgleoadmingleo2user

测试脚本

# 权限失败 curl -X GET 'http://localhost:8000/api/user?username=guest' # 权限成功 curl -X GET 'http://localhost:8000/api/user?username=leo' # 权限成功 curl -X DELETE 'http://localhost:8000/api/user?username=leo' # 权限失败 curl -X DELETE 'http://localhost:8000/api/user?username=leo2'

测试结果

3. 添加权限管理API

以下使用上一章casbin_service提供的方法,示例API如下,可进一步定制

3.1 获取所有用户 // 获取所有用户 auth.GET("/casbin/users", func(ctx *gin.Context) { ctx.JSON(200, casbinService.GetUsers()) }) 3.2 获取所有角色组 // 获取所有角色组 auth.GET("/casbin/roles", func(ctx *gin.Context) { ctx.JSON(200, casbinService.GetRoles()) }) 3.3 获取所有角色组的策略 // 获取所有角色组的策略 auth.GET("/casbin/rolepolicy", func(ctx *gin.Context) { roles, err := casbinService.GetRolePolicy() if err != nil { ctx.String(500, "获取所有角色及权限失败: "+err.Error()) } else { ctx.JSON(200, roles) } }) 3.4 修改角色组策略 /* 修改角色组策略 type RolePolicy struct { RoleName string `gorm:"column:v0"` Url string `gorm:"column:v1"` Method string `gorm:"column:v2"` } */ auth.POST("/casbin/rolepolicy", func(ctx *gin.Context) { var p RolePolicy ctx.BindJSON(&p) err := casbinService.CreateRolePolicy(p) if err != nil { ctx.String(500, "创建角色策略失败: "+err.Error()) } else { ctx.JSON(200, "成功!") } }) 3.5 删除角色组策略 /* 删除角色组策略 type RolePolicy struct { RoleName string `gorm:"column:v0"` Url string `gorm:"column:v1"` Method string `gorm:"column:v2"` } */ auth.DELETE("/casbin/rolepolicy", func(ctx *gin.Context) { var p RolePolicy ctx.BindJSON(&p) err := casbinService.DeleteRolePolicy(p) if err != nil { ctx.String(500, "删除角色策略失败: "+err.Error()) } else { ctx.JSON(200, "成功!") } }) 3.6 添加用户到组 // 添加用户到组, /casbin/user-role?username=leo&rolename=admin auth.POST("/casbin/user-role", func(ctx *gin.Context) { username := ctx.Query("username") rolename := ctx.Query("rolename") err := casbinService.UpdateUserRole(username, rolename) if err != nil { ctx.String(500, "添加用户到组失败: "+err.Error()) } else { ctx.JSON(200, "成功!") } }) 3.7 从组中删除用户 // 从组中删除用户, /casbin/user-role?username=leo&rolename=admin auth.DELETE("/casbin/user-role", func(ctx *gin.Context) { username := ctx.Query("username") rolename := ctx.Query("rolename") err := casbinService.DeleteUserRole(username, rolename) if err != nil { ctx.String(500, "从组中删除用户失败: "+err.Error()) } else { ctx.JSON(200, "成功!") } }) 3.8 测试API

因为这些API也用到了casbin_auth,需要自行准备下权限 > 上述API测试两个,不一一列举

4. 最终目录结构和代码

目录结构

├── casbin.go ├── go.mod ├── go.sum ├── main.go ├── middleware.go └── test.db 4.1 main.go package main import ( "github /gin-gonic/gin" "github /glebarez/sqlite" "gorm.io/gorm" ) func main() { db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) if err != nil { panic("failed to connect database: " + err.Error()) } casbinService, err := NewCasbinService(db) if err != nil { panic("failed to new casbin service: " + err.Error()) } r := gin.Default() auth := r.Group("/") auth.Use(NewCasbinAuth(casbinService)) auth.GET("/api/user", func(ctx *gin.Context) { ctx.String(200, "get /api/user success") }) auth.DELETE("/api/user", func(ctx *gin.Context) { ctx.String(200, "delete /api/user success") }) // 获取所有用户 auth.GET("/casbin/users", func(ctx *gin.Context) { ctx.JSON(200, casbinService.GetUsers()) }) // 获取所有角色组 auth.GET("/casbin/roles", func(ctx *gin.Context) { ctx.JSON(200, casbinService.GetRoles()) }) // 获取所有角色组的策略 auth.GET("/casbin/rolepolicy", func(ctx *gin.Context) { roles, err := casbinService.GetRolePolicy() if err != nil { ctx.String(500, "获取所有角色及权限失败: "+err.Error()) } else { ctx.JSON(200, roles) } }) /* 修改角色组策略 type RolePolicy struct { RoleName string `gorm:"column:v0"` Url string `gorm:"column:v1"` Method string `gorm:"column:v2"` } */ auth.POST("/casbin/rolepolicy", func(ctx *gin.Context) { var p RolePolicy ctx.BindJSON(&p) err := casbinService.CreateRolePolicy(p) if err != nil { ctx.String(500, "创建角色策略失败: "+err.Error()) } else { ctx.JSON(200, "成功!") } }) /* 删除角色组策略 type RolePolicy struct { RoleName string `gorm:"column:v0"` Url string `gorm:"column:v1"` Method string `gorm:"column:v2"` } */ auth.DELETE("/casbin/rolepolicy", func(ctx *gin.Context) { var p RolePolicy ctx.BindJSON(&p) err := casbinService.DeleteRolePolicy(p) if err != nil { ctx.String(500, "删除角色策略失败: "+err.Error()) } else { ctx.JSON(200, "成功!") } }) // 添加用户到组, /casbin/user-role?username=leo&rolename=admin auth.POST("/casbin/user-role", func(ctx *gin.Context) { username := ctx.Query("username") rolename := ctx.Query("rolename") err := casbinService.UpdateUserRole(username, rolename) if err != nil { ctx.String(500, "添加用户到组失败: "+err.Error()) } else { ctx.JSON(200, "成功!") } }) // 从组中删除用户, /casbin/user-role?username=leo&rolename=admin auth.DELETE("/casbin/user-role", func(ctx *gin.Context) { username := ctx.Query("username") rolename := ctx.Query("rolename") err := casbinService.DeleteUserRole(username, rolename) if err != nil { ctx.String(500, "从组中删除用户失败: "+err.Error()) } else { ctx.JSON(200, "成功!") } }) r.Run(":8000") } 4.2 casbin.go package main import ( "github /casbin/casbin/v2" "github /casbin/casbin/v2/model" gormadapter "github /casbin/gorm-adapter/v3" "gorm.io/gorm" ) /* 按如下约定: 1. 所有策略只针对角色组设置 2. 用户关联到组(一个用户可以有多个组) +-------+-------+-----------+--------+----+----+----+ | ptype | v0 | v1 | v2 | v3 | v4 | v5 | +-------+-------+-----------+--------+----+----+----+ | p | admin | /api/user | GET | | | | +-------+-------+-----------+--------+----+----+----+ | p | admin | /api/user | DELETE | | | | +-------+-------+-----------+--------+----+----+----+ | p | user | /api/user | GET | | | | +-------+-------+-----------+--------+----+----+----+ | ... | ... | ... | | | | | +-------+-------+-----------+--------+----+----+----+ | g | leo | admin | | | | | +-------+-------+-----------+--------+----+----+----+ | g | leo2 | admin | | | | | +-------+-------+-----------+--------+----+----+----+ | g | leo3 | user | | | | | +-------+-------+-----------+--------+----+----+----+ */ type CasbinService struct { enforcer *casbin.Enforcer adapter *gormadapter.Adapter } func NewCasbinService(db *gorm.DB) (*CasbinService, error) { a, err := gormadapter.NewAdapterByDB(db) if err != nil { return nil, err } m, err := model.NewModelFromString(`[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) && keyMatch2(r.obj,p.obj) && r.act == p.act`) if err != nil { return nil, err } e, err := casbin.NewEnforcer(m, a) if err != nil { return nil, err } return &CasbinService{adapter: a, enforcer: e}, nil } // (RoleName, Url, Method) 对应于 `CasbinRule` 表中的 (v0, v1, v2) type RolePolicy struct { RoleName string `gorm:"column:v0"` Url string `gorm:"column:v1"` Method string `gorm:"column:v2"` } // 获取所有角色组 func (c *CasbinService) GetRoles() []string { return c.enforcer.GetAllRoles() } // 获取所有角色组权限 func (c *CasbinService) GetRolePolicy() (roles []RolePolicy, err error) { err = c.adapter.GetDb().Model(&gormadapter.CasbinRule{}).Where("ptype = 'p'").Find(&roles).Error if err != nil { return nil, err } return } // 创建角色组权限, 已有的会忽略 func (c *CasbinService) CreateRolePolicy(r RolePolicy) error { // 不直接操作数据库,利用enforcer简化操作 err := c.enforcer.LoadPolicy() if err != nil { return err } _, err = c.enforcer.AddPolicy(r.RoleName, r.Url, r.Method) if err != nil { return err } return c.enforcer.SavePolicy() } // 修改角色组权限 func (c *CasbinService) UpdateRolePolicy(old, new RolePolicy) error { _, err := c.enforcer.UpdatePolicy([]string{old.RoleName, old.Url, old.Method}, []string{new.RoleName, new.Url, new.Method}) if err != nil { return err } return c.enforcer.SavePolicy() } // 删除角色组权限 func (c *CasbinService) DeleteRolePolicy(r RolePolicy) error { _, err := c.enforcer.RemovePolicy(r.RoleName, r.Url, r.Method) if err != nil { return err } return c.enforcer.SavePolicy() } type User struct { UserName string RoleNames []string } // 获取所有用户以及关联的角色 func (c *CasbinService) GetUsers() (users []User) { p := c.enforcer.GetGroupingPolicy() usernameUser := make(map[string]*User, 0) for _, _p := range p { username, usergroup := _p[0], _p[1] if v, ok := usernameUser[username]; ok { usernameUser[username].RoleNames = append(v.RoleNames, usergroup) } else { usernameUser[username] = &User{UserName: username, RoleNames: []string{usergroup}} } } for _, v := range usernameUser { users = append(users, *v) } return } // 角色组中添加用户, 没有组默认创建 func (c *CasbinService) UpdateUserRole(username, rolename string) error { _, err := c.enforcer.AddGroupingPolicy(username, rolename) if err != nil { return err } return c.enforcer.SavePolicy() } // 角色组中删除用户 func (c *CasbinService) DeleteUserRole(username, rolename string) error { _, err := c.enforcer.RemoveGroupingPolicy(username, rolename) if err != nil { return err } return c.enforcer.SavePolicy() } // 验证用户权限 func (c *CasbinService) CanAccess(username, url, method string) (ok bool, err error) { return c.enforcer.Enforce(username, url, method) } 4.3 middleware.go package main import ( "log" "github /gin-gonic/gin" ) func NewCasbinAuth(srv *CasbinService) gin.HandlerFunc { return func(ctx *gin.Context) { err := srv.enforcer.LoadPolicy() if err != nil { ctx.String(500, err.Error()) ctx.Abort() return } // 简便起见,假设用户从url传递 /xxxx?username=leo,实际应用可以结合jwt等鉴权 username, _ := ctx.GetQuery("username") log.Println(username, ctx.Request.URL.Path, ctx.Request.Method) ok, err := srv.enforcer.Enforce(username, ctx.Request.URL.Path, ctx.Request.Method) if err != nil { ctx.String(500, err.Error()) ctx.Abort() return } else if !ok { ctx.String(403, "验证权限失败!") ctx.Abort() return } ctx.Next() } } 5. 更进一步

主要记录一下casbin概念和使用经验,距离业务使用还有以下等需要调整

用户身份识别是通过URL参数username获得,实际使用中可配合jwt、session等使用API接口可根据业务规范调整重写用户其他信息需要关联到casbin_rule表增加前端界面操作管理权限…
标签:

GoGinGormCasbin权限管理实现-3.实现Gin鉴权中间件由讯客互联IT业界栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“GoGinGormCasbin权限管理实现-3.实现Gin鉴权中间件