漏洞赏金猎人笔记-GraphQL-III

漏洞赏金猎人笔记-GraphQL-III

声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。

前言

前面介绍了query、mutation这两种类型的操作,今天将要介绍最后一种类型的操作:subscription

这里对这三种类型的操作做一个简单的总结:

  • query 查询:获取数据,比如查找,CRUD 中的 R
  • mutation 变更:对数据进行变更,比如增加、删除、修改,CRUD 中的 CUD
  • substription 订阅:当数据发生更改,进行消息推送

此外,这三种类型都需要由解析函数 Resolver 来提供数据,比如

query {
hi
}

那么同名的解析函数resolver(一般也是写在后端)应该是这样的

Query: {
hi (parent, args, context, info) {
return ...
}
}

解析函数接受四个参数,分别为

  • parent:当前上一个解析函数的返回值
  • args:查询中传入的参数
  • context:提供给所有解析器的上下文信息
  • info:一个保存与当前查询相关的字段特定信息以及 schema 详细信息的值

解析函数的返回值可以是一个具体的值,也可以是 Promise 或 Promise 数组。

此外还需要知道Schema这个文件:

它定义了字段的类型、数据的结构,描述了接口数据请求的规则,当我们进行一些错误的查询的时候 GraphQL 引擎会负责告诉我们哪里有问题,和详细的错误信息,对开发调试十分友好。

Schema一般使用一个简单的强类型模式语法,称为模式描述语言(Schema Definition Language, SDL)

# src/schema.graphql

#
Query 入口
type Query {
hello: String
users: [User]!
user(id: String): [User]!
}

#
Mutation 入口
type Mutation {
createUser(id: ID!, name: String!, email: String!, age: Int,gender: Gender): User!
updateUser(id: ID!, name: String, email: String, age: Int, gender: Gender): User!
deleteUser(id: ID!): User
}

#
Subscription 入口
type Subscription {
subsUser(id: ID!): User
}

type User implements UserInterface {
id: ID!
name: String!
age: Int
gender: Gender
email: String!
}

#
枚举类型
enum Gender {
MAN
WOMAN
}

#
接口类型
interface UserInterface {
id: ID!
name: String!
age: Int
gender: Gender
}

这里的 Schema 文件(这个文件一般写在后端)从 Query、Mutation、Subscription 入口开始定义了各个对象类型或标量类型,这些字段的类型也可能是其他的对象类型或标量类型,组成一个树形的结构,而用户在向服务端发送请求的时候,沿着这个树选择一个或多个分支就可以获取多组信息。

正文

这里主要通过一个实例来理解,先看个图来理解大体上的一个逻辑:

漏洞赏金猎人笔记-GraphQL-III

img.png

通过订阅这样的一个操作在服务端注册了了一个订阅查询,如果服务端有对应的更新,就会触发这个所订阅的查询,最后会在客户端实时更新。

下面通过一个例子来理解一下Subscription:

当前端发起订阅请求之后,如果后端发现数据改变,可以给前端推送实时信息。

Schema文件:

# Subscription 入口
type Subscription {
subsUser(id: ID!): User
}

type User {
id: ID!
name: String!
age: Int
email: String!
}


对应resolver:



import Db from '../db'

const { PubSub, withFilter } = require('apollo-server')
const pubsub = new PubSub()
const USER_UPDATE_CHANNEL = 'USER_UPDATE'

export default {
Mutation: {
updateUser: (parent, { id, name, email, age }) => Db.user({ id })
.then(existUser => {
if (!existUser)
throw new Error('没有这个id的人')
return existUser
})
.then(() => Db.updateUser({ id, name, email, age }))
.then(user => {
pubsub.publish(USER_UPDATE_CHANNEL, { subsUser: user })
return user
})
},
Subscription: {
subsUser: {
subscribe: withFilter(
(parent, { id }) => pubsub.asyncIterator(USER_UPDATE_CHANNEL),
(payload, variables) => payload.subsUser.id === variables.id
),
resolve: (payload, variables) => {
console.log('🚢 接收到数据:', payload)
}
}
}
}

这里的 pubsub 是 apollo-server 里负责订阅和发布的类,它在接受订阅时提供一个异步迭代器,在后端觉得需要发布订阅的时候向前端发布 payload。withFilter 的作用是过滤掉不需要的订阅消息

我们发布一个订阅请求:

# 请求
subscription subsUser($id: ID!) {
subsUser(id: $id) {
id
name
age
email
}
}

#
参数
{ "id": "2" }

用刚刚的数据更新操作来进行一次数据的更改,然后我们将获取到并打印出 pubsub.publish 发布的 payload,这样就完成了数据订阅。

全文完结。

参考

https://www.apollographql.com/docs/react/data/subscriptions/

https://dgraph.io/blog/post/how-does-graphql-subscription/

https://github.com/apollographql/docs-examples/blob/main/apollo-server/v3/subscriptions-graphql-ws/src/index.ts#L14


原文始发于微信公众号(迪哥讲事):漏洞赏金猎人笔记-GraphQL-III

版权声明:admin 发表于 2022年11月25日 下午6:39。
转载请注明:漏洞赏金猎人笔记-GraphQL-III | CTF导航

相关文章

暂无评论

暂无评论...