ivandotv/graphql-no-alias
GitHub: ivandotv/graphql-no-alias
GraphQL 服务端验证库,通过限制 alias 字段重复调用次数来防止批量查询攻击和 DoS。
Stars: 12 | Forks: 3
# GraphQL No Alias Directive 验证
[](https://github.com/ivandotv/graphql-no-alias/actions/workflows/CI.yml)
[](https://app.codecov.io/gh/ivandotv/graphql-no-alias)
[](https://github.com/ivandotv/graphql-no-alias/blob/main/LICENSE)
- [安装](#instalation)
- [用法](#usage)
- [使用 directive](#using-the-directive)
- [Schema 设置](#schema-setup)
- [Object type 用法](#object-type-usage)
- [Field type 用法](#field-type-usage)
- [自定义声明](#customizing-the-declaration)
- [命令式配置](#imperative-configuration)
- [自定义错误消息](#customizing-the-error-message)
- [Envelop 插件](#envelop-plugin)
- [No Batched Queries 库](#no-batched-queries-library)
- [许可证](#license)
Graphql 验证以及附带的 directive,用于限制可以发送到 GraphQL 服务器的 `alias` 查询和 mutation 的数量。
它将阻止类似于以下形式的某些攻击。
```
// batch query attack (hello DoS)
query {
getUsers(first: 1000)
second: getUsers(first: 2000)
third: getUsers(first: 3000)
fourth: getUsers(first: 4000)
}
// or batch login attack
mutation {
login(pass: 1111, username: "ivan")
second: login(pass: 2222, username: "ivan")
third: login(pass: 3333, username: "ivan")
fourth: login(pass: 4444, username: "ivan")
}
`
```
你可以在这里阅读更多关于批量攻击的内容:https://lab.wallarm.com/graphql-batching-attack/
## 安装
```
npm i graphql-no-alias
```
## 用法
有两种方式可以使用此验证:
- 在 `schema` 中使用 `directive`
- [使用配置选项](#Imperative-configuration)(性能更好)
### 使用 directive
包含两个部分:需要添加到 `schema` 中的 `@noAlias` directive,以及需要添加到 GraphQL 服务器 `validationRules` 数组中的验证函数。
在接下来的示例中,`hello` 查询将被允许在每个请求中调用 2 次,而通过直接在 `Mutation` type 上设置 `@noAlias` directive,所有的 mutation 将被限制为每次 mutation 只能调用 1 次。
```
const express = require('express')
const { graphqlHTTP } = require('express-graphql')
const { buildSchema } = require('graphql')
const { createValidation } = require('graphql-no-alias')
// get the validation function and type definition of the declaration
const { typeDefs, validation } = createValidation()
//add type defintion to schema
const schema = buildSchema(`
${typeDefs}
type Query {
hello: String @noAlias(allow:2)
}
type Mutation @noAlias {
muteOne(n:Int):String
}
`)
const app = express()
app.use(
'/graphql',
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
validationRules: [validation] //add the validation function
})
)
app.listen(4000)
```
### Schema 设置
该声明可以用于 object `type`(Query 或 Mutation)或 type `fields`(特定的 query 或 mutation)。当该声明用于 `type` 时,它会影响该 type(Query 或 Mutation)的所有 fields。
#### Object type 用法
在接下来的示例中,**所有**查询将被限制为**仅一次调用**。
```
const schema = buildSchema(`
type Query @noAlias {
getUser: User
getFriends: [User]!
}
`)
```
客户端请求:
```
query {
getUser
alias_get_user: getUser // Error - validation fails
getFriends
alias_get_friends: getFriends // Error - validation fails
}
```
该 directive 还接受一个参数 `allow`,用于声明允许的默认 alias 数量。
在接下来的示例中,所有查询将被允许调用 `3` 次(一次原始调用,两次 alias)
```
var schema = buildSchema(`
type Query @noAlias(allow:3) {
getUser: User
getFriends: [User]!
}
`)
```
在客户端:
```
query {
getUser
alias_2: getUser
alias_3: getUser
alias_4: getUser // Error - validation fails
}
```
#### Field type 用法
在 type fields 上的用法与在 object type 上相同,区别在于,当与 object directive 结合使用时,field 上的 directive 将具有优先权。
在接下来的示例中,所有 query fields 将被允许进行 `3` 次批量调用,但 `getFriends` 查询除外,它只能被调用 `1` 次。
```
var schema = buildSchema(`
type Query @noAlias(allow:3) {
getUser: User
getFriends: [User]! @noAlias(allow:1) //same as @noAlias
}
`)
```
客户端请求:
```
query {
getUser
alias_2: getUser
alias_3: getUser
getFriends
alias_1: getFriends // Error - validation fails
}
```
### 自定义声明
可以自定义声明以拥有不同的名称和不同的默认 `allow` 值,还可以向其传递一个自定义错误函数,该函数会在验证失败时执行。
在接下来的示例中,`validation` 默认将允许对同一 field 调用 `3` 次,directive 名称将更改为 `NoBatchCalls`,并且会有一条自定义错误消息。
```
const defaultAllow = 3
const directiveName = 'NoBatchCalls'
const { typeDefs, validation } = createValidation({
defaultAllow,
directiveName
})
```
用法:
```
const schema = buildSchema(`
type Query @noBatchCalls {
getUser: User @noBatchCalls(allow:4)
getFriends: [User]!
}
`)
```
### 命令式配置
使用命令式配置,无需进行类型定义和 schema 修改。相反,我们使用一个配置对象。
这带来了更好的性能,因为不需要分析 `schema`(不需要寻找 directive)。
```
const permissions = {
Query: {
'*': 2, // default value for all queries
getAnotherUser: 5 // custom value for specific query
},
Mutation: {
'*': 1 //default value for all mutations
}
}
const { validation } = createValidation({ permissions })
const schema = buildSchema(/* GraphQL */ `
type Query {
getUser: User
getAnotherUser: User
}
type User {
name: String
}
`)
const app = express()
app.use(
'/graphql',
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
validationRules: [validation] //add the validation function
})
)
app.listen(4000)
```
请注意,当将 `permissions` 对象传递给配置时,schema directive 将被忽略。
### 自定义错误消息
继续前面的示例,验证失败时报告的 `error` 消息也可以进行自定义。你可以返回一个 `GrahphQLError` 实例,或者只返回一个用作错误消息的 `string`。
```
const { typeDefs, validation } = createValidation({errorFn:(
typeName: string, //type name Query or Mutation
fieldName: string,
maxAllowed: number,
node: FieldNode,
ctx: ValidationContext
): GraphQLError {
return new GraphQLError(
`Hey! allowed number of calls for ${typeName}->${fieldName} has been exceeded (max: ${maxAllowed})`
)
//or return string
return 'custom message'
}
})
```
## Envelop 插件
如果你正在使用 [GraphQL Envelop](https://www.envelop.dev/)。我制作了一个使用此 directive 的[插件](packages/envelop/README.md)。
## No Batched Queries 库
我还创建了另一个验证库:[No batched queries](https://github.com/ivandotv/graphql-no-batched-queries),它限制了每个请求可以发送的**所有**查询和 mutation 的数量。它与本验证配合得很好,因此你可以允许发送例如 3 个查询,然后使用 `noAlias` directive 禁止重复查询。
### 许可证
本项目基于 MIT 许可证授权 - 详情请参阅 [LICENSE](LICENSE) 文件
标签:API安全, GraphQL, JSON输出, MITM代理, Syscall, Web开发, 接口防护, 数据可视化, 自动化攻击, 配置错误, 验证校验