经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
Graphql请求的RBAC权限控制
来源:cnblogs  作者:wang_yb  时间:2020/11/9 16:09:43  对本文有异议

概要

Graphql 请求灵活性远非 RestFul 的请求能比, 但是 Graphql 的 Endpoint 一般都是统一的一个, 根据 body 中的请求内容和参数来决定返回何种数据.

因此, 对于 Graphql 请求的权限控制不能像 RestFul 请求那样根据请求的 URL 和 method 来判断是否有权限. 关于 Graphql 的权限判断, 曾经困扰了好长一段时间, 一直没有找到合适的方式来判断.

直至最近一段时间, 在 github.com/graphql-go 这个库中找到能够解析 graphql 请求的方法, 然后结合 casbin 的 rbac 模型, 才算是将 Graphql 权限问题的解决推进了一大步.

实现方式

实现 Graphql 的权限认证, 主要包含以下 3 个部分:

  1. adapter: 用来连接 graphql-engine 的 casbin adapter, 也就是可以将权限策略持久存储到数据库
  2. middleware: 基于 golang gin 框架的中间件, 拦截请求并判断其是否符合权限要求
  3. 权限 api: 提供操作权限的 API, 基于 RBAC 的, 所以只提供的基础的几个接口

adapter

casbin 默认是将权限策略存储在 csv 文件中的, 这只能在 demo 中用用, 实际系统用明显不合适. 因此, 我们需要写个 adapter, 将权限策略写入数据库.

adapter 很简单, 只要仿照 casbin 已有的那些 adapter 实现相应的接口即可:

  1. 1 package auth
  2. 2
  3. 3 import (
  4. 4 "runtime"
  5. 5
  6. 6 imodel "illuminant/model"
  7. 7
  8. 8 "github.com/casbin/casbin/v2/model"
  9. 9 "github.com/casbin/casbin/v2/persist"
  10. 10 )
  11. 11
  12. 12 // Adapter represents the hasura graphql
  13. 13 type Adapter struct{}
  14. 14
  15. 15 // finalizer is the destructor for Adapter.
  16. 16 func finalizer(a *Adapter) {}
  17. 17
  18. 18 // NewAdapter is the constructor for Adapter.
  19. 19 func NewAdapter() (*Adapter, error) {
  20. 20 a := &Adapter{}
  21. 21
  22. 22 // Call the destructor when the object is released.
  23. 23 runtime.SetFinalizer(a, finalizer)
  24. 24
  25. 25 return a, nil
  26. 26 }
  27. 27
  28. 28 func loadPolicyLine(line *imodel.CasbinRule, model model.Model) {
  29. 29 lineText := line.PType
  30. 30 if line.V0 != "" {
  31. 31 lineText += ", " + line.V0
  32. 32 }
  33. 33 if line.V1 != "" {
  34. 34 lineText += ", " + line.V1
  35. 35 }
  36. 36 if line.V2 != "" {
  37. 37 lineText += ", " + line.V2
  38. 38 }
  39. 39 if line.V3 != "" {
  40. 40 lineText += ", " + line.V3
  41. 41 }
  42. 42 if line.V4 != "" {
  43. 43 lineText += ", " + line.V4
  44. 44 }
  45. 45 if line.V5 != "" {
  46. 46 lineText += ", " + line.V5
  47. 47 }
  48. 48
  49. 49 persist.LoadPolicyLine(lineText, model)
  50. 50 }
  51. 51
  52. 52 // LoadPolicy loads policy from database.
  53. 53 func (a *Adapter) LoadPolicy(model model.Model) error {
  54. 54 lines, err := GetRules()
  55. 55 if err != nil {
  56. 56 return err
  57. 57 }
  58. 58
  59. 59 for _, line := range lines {
  60. 60 loadPolicyLine(line, model)
  61. 61 }
  62. 62
  63. 63 return nil
  64. 64 }
  65. 65
  66. 66 func savePolicyLine(ptype string, rule []string) imodel.CasbinRule {
  67. 67 line := imodel.CasbinRule{}
  68. 68
  69. 69 line.PType = ptype
  70. 70 if len(rule) > 0 {
  71. 71 line.V0 = rule[0]
  72. 72 }
  73. 73 if len(rule) > 1 {
  74. 74 line.V1 = rule[1]
  75. 75 }
  76. 76 if len(rule) > 2 {
  77. 77 line.V2 = rule[2]
  78. 78 }
  79. 79 if len(rule) > 3 {
  80. 80 line.V3 = rule[3]
  81. 81 }
  82. 82 if len(rule) > 4 {
  83. 83 line.V4 = rule[4]
  84. 84 }
  85. 85 if len(rule) > 5 {
  86. 86 line.V5 = rule[5]
  87. 87 }
  88. 88
  89. 89 return line
  90. 90 }
  91. 91
  92. 92 // SavePolicy saves policy to database.
  93. 93 func (a *Adapter) SavePolicy(model model.Model) error {
  94. 94 err := DeleteRules(imodel.CasbinRule{})
  95. 95 if err != nil {
  96. 96 return err
  97. 97 }
  98. 98
  99. 99 var lines = make([]imodel.CasbinRule, 0)
  100. 100
  101. 101 for ptype, ast := range model["p"] {
  102. 102 for _, rule := range ast.Policy {
  103. 103 line := savePolicyLine(ptype, rule)
  104. 104 lines = append(lines, line)
  105. 105 }
  106. 106 }
  107. 107
  108. 108 for ptype, ast := range model["g"] {
  109. 109 for _, rule := range ast.Policy {
  110. 110 line := savePolicyLine(ptype, rule)
  111. 111 lines = append(lines, line)
  112. 112 }
  113. 113 }
  114. 114
  115. 115 return AddRules(lines)
  116. 116 }
  117. 117
  118. 118 // AddPolicy adds a policy rule to the storage.
  119. 119 func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
  120. 120 line := savePolicyLine(ptype, rule)
  121. 121 return AddRule(line)
  122. 122 }
  123. 123
  124. 124 // RemovePolicy removes a policy rule from the storage.
  125. 125 func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
  126. 126 line := savePolicyLine(ptype, rule)
  127. 127 return DeleteRules(line)
  128. 128 }
  129. 129
  130. 130 // RemoveFilteredPolicy removes policy rules that match the filter from the storage.
  131. 131 func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
  132. 132 line := imodel.CasbinRule{}
  133. 133
  134. 134 line.PType = ptype
  135. 135 filter := []string{}
  136. 136 filter = append(filter, "p_type")
  137. 137 if fieldIndex <= 0 && 0 < fieldIndex+len(fieldValues) {
  138. 138 line.V0 = fieldValues[0-fieldIndex]
  139. 139 filter = append(filter, "v0")
  140. 140 }
  141. 141 if fieldIndex <= 1 && 1 < fieldIndex+len(fieldValues) {
  142. 142 line.V1 = fieldValues[1-fieldIndex]
  143. 143 filter = append(filter, "v1")
  144. 144 }
  145. 145 if fieldIndex <= 2 && 2 < fieldIndex+len(fieldValues) {
  146. 146 line.V2 = fieldValues[2-fieldIndex]
  147. 147 filter = append(filter, "v2")
  148. 148 }
  149. 149 if fieldIndex <= 3 && 3 < fieldIndex+len(fieldValues) {
  150. 150 line.V3 = fieldValues[3-fieldIndex]
  151. 151 filter = append(filter, "v3")
  152. 152 }
  153. 153 if fieldIndex <= 4 && 4 < fieldIndex+len(fieldValues) {
  154. 154 line.V4 = fieldValues[4-fieldIndex]
  155. 155 filter = append(filter, "v4")
  156. 156 }
  157. 157 if fieldIndex <= 5 && 5 < fieldIndex+len(fieldValues) {
  158. 158 line.V5 = fieldValues[5-fieldIndex]
  159. 159 filter = append(filter, "v5")
  160. 160 }
  161. 161
  162. 162 return DeleteRules(line)
  163. 163 }

其中, GetRules, DeleteRules, AddRule, AddRules 是实际和 graphql-engin 交互的函数.

middleware

基于 golang gin 的中间件, 目的是将权限检查和业务 API 的职责分开, 便于开发和维护.

  1. 1 package middleware
  2. 2
  3. 3 import (
  4. 4 "bytes"
  5. 5 "illuminant/config"
  6. 6 "illuminant/logger"
  7. 7 "illuminant/middleware/auth"
  8. 8 "illuminant/util"
  9. 9 "io/ioutil"
  10. 10 "strings"
  11. 11
  12. 12 jwt "github.com/appleboy/gin-jwt/v2"
  13. 13 "github.com/casbin/casbin/v2"
  14. 14 "github.com/casbin/casbin/v2/model"
  15. 15 "github.com/gin-gonic/gin"
  16. 16 )
  17. 17
  18. 18 // NewAuthorizer returns the authorizer, uses a Casbin enforcer as input
  19. 19 func NewAuthorizer() gin.HandlerFunc {
  20. 20 cnf := config.GetConfig()
  21. 21 lg := logger.GetLogger()
  22. 22 adp, err := auth.NewAdapter()
  23. 23 if err != nil {
  24. 24 lg.Err(err).Msg("casbin adapter error")
  25. 25 panic(err)
  26. 26 }
  27. 27
  28. 28 m, err := model.NewModelFromString(cnf.Auth.RBACModel)
  29. 29 if err != nil {
  30. 30 lg.Err(err).Msg("casbin model from string error")
  31. 31 panic(err)
  32. 32 }
  33. 33
  34. 34 e, err := casbin.NewEnforcer(m, adp)
  35. 35 if err != nil {
  36. 36 lg.Err(err).Msg("casbin enforcer error")
  37. 37 panic(err)
  38. 38 }
  39. 39
  40. 40 a := &RBACAuthorizer{enforcer: e}
  41. 41 return func(c *gin.Context) {
  42. 42 if !a.CheckPermission(c) {
  43. 43 a.RequirePermission(c)
  44. 44 c.Abort()
  45. 45 }
  46. 46 }
  47. 47 }
  48. 48
  49. 49 // RBACAuthorizer stores the casbin handler
  50. 50 type RBACAuthorizer struct {
  51. 51 enforcer *casbin.Enforcer
  52. 52 }
  53. 53
  54. 54 // CheckPermission checks the user/method/path combination from the request.
  55. 55 // Returns true (permission granted) or false (permission forbidden)
  56. 56 func (a *RBACAuthorizer) CheckPermission(c *gin.Context) bool {
  57. 57 lg := logger.GetLogger()
  58. 58 claims := jwt.ExtractClaims(c)
  59. 59
  60. 60 method := c.Request.Method
  61. 61 path := c.Request.URL.Path
  62. 62
  63. 63 // a.ReloadPermissions()
  64. 64 if strings.Index(path, "/api/v1/graphql") < 0 {
  65. 65 return a.RestFullPermission(claims["id"].(string), path, method)
  66. 66 }
  67. 67
  68. 68 // graphql api
  69. 69 body, err := c.GetRawData()
  70. 70 if err != nil {
  71. 71 lg.Err(err).Msg("get body raw data")
  72. 72 return false
  73. 73 }
  74. 74 c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
  75. 75
  76. 76 return a.GraphqlPermission(claims["id"].(string), body)
  77. 77 }
  78. 78
  79. 79 // RequirePermission returns the 403 Forbidden to the client
  80. 80 func (a *RBACAuthorizer) RequirePermission(c *gin.Context) {
  81. 81 util.Fail(c, util.AUTH_PERMISSION_DENIED, "权限不足", nil)
  82. 82 }
  83. 83
  84. 84 func (a *RBACAuthorizer) ReloadPermissions() {
  85. 85 a.enforcer.LoadPolicy()
  86. 86 }
  87. 87
  88. 88 func (a *RBACAuthorizer) RestFullPermission(userId, path, method string) bool {
  89. 89 lg := logger.GetLogger()
  90. 90
  91. 91 allowed, err := a.enforcer.Enforce(userId, path, method)
  92. 92 if err != nil {
  93. 93 lg.Err(err).Msg("RestFullPermission check error")
  94. 94 return false
  95. 95 }
  96. 96
  97. 97 return allowed
  98. 98 }
  99. 99
  100. 100 func (a *RBACAuthorizer) GraphqlPermission(userId string, body []byte) bool {
  101. 101 lg := logger.GetLogger()
  102. 102
  103. 103 funcs, err := util.GetGraphqlFunc(body)
  104. 104 if err != nil {
  105. 105 lg.Err(err).Msg("GetGraphqlFunc error")
  106. 106 return false
  107. 107 }
  108. 108
  109. 109 for _, f := range funcs {
  110. 110 allowed, err := a.enforcer.Enforce(userId, f, "*")
  111. 111 if err != nil {
  112. 112 lg.Err(err).Msg("GraphqlPermission check error")
  113. 113 return false
  114. 114 }
  115. 115
  116. 116 if !allowed {
  117. 117 return allowed
  118. 118 }
  119. 119 }
  120. 120
  121. 121 return true
  122. 122 }

这个中间件同时支持 RestFul 和 Graphql 的接口, 只要将相应的策略存入数据库即可.

基础权限 API

基础的 API 主要有 5 个:

  1. 获取某个角色的所有权限
  2. 给角色增加权限
  3. 给角色删除权限
  4. 给用户添加角色
  5. 给用户删除角色

有这 5 个基础 API 之后, 基本就满足了管理 casbin 中 RBAC 策略的需求了.

总结

上面的功能是在我自己一个正在逐步完善的一个后端快速开发平台上是实现的, 所有代码都在那个平台上.
即: illuminant的代码 以及 illuminant 的文档

上面的代码位于: middleware 模块, middleware 模块下的 auth 模块 以及 controller 模块下的 auth_controller.go

原文链接:http://www.cnblogs.com/wang_yb/p/13886551.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号