概述
通過 graphql 請求資料時, where條件是自己寫在graphql請求字串中的, 所以獲取多少資料后端無法控制, 比如
{
blogs(where: {user_id: {_eq: "xxxxxx"}}){
id
title
content
user_id
}
}
通過 where 條件, 雖然可以獲取只屬于自己的 blog 資訊.
但是, 這樣帶來了2個問題:
- 要把 user_id (有時候是role_id) 資訊暴露給前端
- 前端可以不加where條件, 從而獲取所有blog, 包括其他用戶的 blog
解決方案
為了解決上述問題, 在 illuminant 框架中實作了一個 修改請求中 where 條件的中間件.
首先, 用戶的 user_id/role_id 資訊可以放在 jwt-token 中(配合已有的 JWT 中間件), 不顯式的在response中回傳前端.
然后, 配置需要進行 user_id/role_id 的條件注入的函式(比如上面的blogs), 此中間件在執行 graphql 之前, 把包含 user_id/role_id 的where 條件注入到 graphql 請求的函式中.

Where 中間件定義
type HasuraWhere struct {
GQLKey string // 注入到graphql請求中where條件的key, 一般就是 user_id 或者 role_id
JWTKey string // 對應到 jwt 中的key, 一般就是 user_id 或者 role_id
CheckGQLFuncs []string // 需要注入 where 條件的 graphql 函式
}
上面三個欄位也就是 Where中間件的配置資訊.
Where中間件處理流程
func (hw *HasuraWhere) AddWhereCondition(c *gin.Context) {
lg := logger.GetLogger()
claims := jwt.ExtractClaims(c)
path := c.Request.URL.Path
// 如果不是 graphql 請求, 直接回傳
if strings.Index(path, "/api/v1/graphql") < 0 {
return
}
// graphql api
body, err := c.GetRawData()
if err != nil {
lg.Err(err).Msg("HasuraWhere middleware: AddWhereCondition")
return
}
// 核心處理
newBody, err := util.AddWhereCondition(body, hw.GQLKey, claims[hw.JWTKey].(string), hw.CheckGQLFuncs)
if err != nil {
lg.Err(err).Msg("HasuraWhere middleware: AddWhereCondition")
return
}
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(newBody))
c.Request.ContentLength = int64(len(newBody))
}
util package 下的 AddWhereCondition 包含主要處理邏輯
func AddWhereCondition(body []byte, key, val string, gqlFuncs []string) ([]byte, error) {
var req gqlRequest
if err := json.Unmarshal(body, &req); err != nil {
return nil, err
}
// req.Query: 請求中原始的 graphql 字串
// key: 需要注入到where條件中的 key
// val: 需要注入到where條件中的 value
// gqlFuncs: 需要進行注入操作的 graphql 函式
newQuery := changeFields(req.Query, key, val, gqlFuncs)
req.Query = newQuery
return json.Marshal(&req)
}
graphql 的AST決議和修改依賴 github.com/graphql-go/graphql
// 函式已有where條件, 將key/val 注入到已有的where中
func changeWithWhere(arg *sourceAST.Argument, key, val string) {
whereValue := arg.Value.GetValue().([]*sourceAST.ObjectField)
injectClause := createInjectCondition(key, val)
whereValue = https://www.cnblogs.com/wang_yb/p/append(whereValue, injectClause)
fmt.Printf("where value: %#v\n", whereValue)
arg.Value = https://www.cnblogs.com/wang_yb/p/sourceAST.NewObjectValue(&sourceAST.ObjectValue{
Fields: whereValue,
})
}
// 函式沒有where條件, 新增where 條件, 并將 key/val 注入其中
func changeWithoutWhere(node *sourceAST.Field, key, val string) {
injectClause := createInjectCondition(key, val)
arg := sourceAST.NewArgument(&sourceAST.Argument{
Name: sourceAST.NewName(&sourceAST.Name{Value:"where"}),
Value: sourceAST.NewObjectValue(&sourceAST.ObjectValue{
Fields: []*sourceAST.ObjectField{injectClause},
}),
})
if node.Arguments == nil {
node.Arguments = make([]*sourceAST.Argument, 0)
}
node.Arguments = append(node.Arguments, arg)
}
// 根據 key/val 創建 graphql where條件的結構
func createInjectCondition(key, val string) *sourceAST.ObjectField {
return sourceAST.NewObjectField(&sourceAST.ObjectField{
Name: sourceAST.NewName(&sourceAST.Name{Value: key}),
Value: sourceAST.NewObjectValue(&sourceAST.ObjectValue{
Fields: []*sourceAST.ObjectField{
sourceAST.NewObjectField(&sourceAST.ObjectField{
Name: sourceAST.NewName(&sourceAST.Name{Value: "_eq"}),
Value: sourceAST.NewStringValue(&sourceAST.StringValue{Value: val}),
}),
},
}),
})
}
實作效果
將此中間件和JWT中間件一起在路由中使用:
whereMiddleware := middleware.NewHasuraWhereMiddleware("user_id", "id", []string{"blogs", "blogs2", "blogs3"})
jwtAuthRoutes := r.Use(authMiddleware.MiddlewareFunc(), whereMiddleware)
// ... 省略 ...
jwtAuthRoutes.POST("/graphql", util.ReverseProxy())
請求時的 graphql如下:
query query_blogs($title: String!){
blogs(where: {title: {_eq: "xxx"}}) {
id
title
content
user_id
}
}
{
blogs2 {
id
title
content
user_id
}
}
query query_blogs3($offset: Int!){
blogs3(offset: $offset) {
id
title
content
user_id
}
}
{
not_blogs {
id
title
content
user_id
}
}
經過中間件之后, 實際執行的 graphql 如下:
query query_blogs($title: String!){
blogs(where: {title: {_eq: "xxx"}, user_id: {_eq: "user_id_from_jwt_token"}}) {
id
title
content
user_id
}
}
{
blogs2(where: {user_id: {_eq: "user_id_from_jwt_token"}}) {
id
title
content
user_id
}
}
query query_blogs3($offset: Int!){
blogs3(offset: $offset, where: {user_id: {_eq: "user_id_from_jwt_token"}}) {
id
title
content
user_id
}
}
{
not_blogs {
id
title
content
user_id
}
}
相關代碼
illuminant專案 中:
- /routes/jwt.go
- /middleware/where_middleware.go
- /util/graphql_where.go
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/257009.html
標籤:Go
上一篇:快取設計的好,服務基本不會倒
