• graphql请求中的数据保护(illuminant)


    概述

    通过 graphql 请求数据时, where条件是自己写在graphql请求字符串中的, 所以获取多少数据后端无法控制, 比如

    {
      blogs(where: {user_id: {_eq: "xxxxxx"}}){
        id
        title
        content
        user_id
      }
    }
    

    通过 where 条件, 虽然可以获取只属于自己的 blog 信息.
    但是, 这样带来了2个问题:

    1. 要把 user_id (有时候是role_id) 信息暴露给前端
    2. 前端可以不加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 请求的函数中.

    image.png

    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 = append(whereValue, injectClause)
    	fmt.Printf("where value: %#v
    ", whereValue)
    	arg.Value = 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
  • 相关阅读:
    整理SVN代码-->正式环境的代码
    业务(1)
    在一个项目中跨领域调用接口的的实现
    一个java文件编译之后会产生多个class文件
    shutil模块
    shevle模块
    confiparser模块
    sys模块
    subprocess模块
    【ADO.NET】3、从TXT中导入数据到数据库
  • 原文地址:https://www.cnblogs.com/wang_yb/p/14379364.html
Copyright © 2020-2023  润新知