- 后端
- API网关
- 数据库
- GraphQL
【免费下载链接】graphql-engine
Blazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.
本文围绕 rfcs/reuse-insert-permission-in-action.md 这一 RFC 展开:当业务校验无法用 Hasura 的权限表达式或 Postgres 的 check 约束表达时,如何借助 Actions 调用外部 webhook 完成校验,同时不丢失原有 insert 权限(如
{"channel": {"members": {"user_id": "x-hasura-user-id"}}}这类基于角色的行级约束)的复用能力。读完本文,你将掌握该 RFC 的问题背景、admin_only提案的完整语义,以及仓库中已落地的输入验证(input validations)机制如何最终解决了同一类问题。
一、RFC 要解决的核心矛盾
1.1 场景:用 Hasura 权限表达不了的校验
设想一张消息表,其结构如下(RFC 原例):
create table message ( id serial primary key, content text not null, channel_id integer not null references channel(id) ) create table channel (..) create table channel_members (..)业务要求:user角色只有在其所属频道的成员列表中时,才能向该频道发消息。这一约束天然可以用 Hasura 的 insert 权限check表达式表达:
{"channel": {"members": {"user_id": "x-hasura-user-id"}}}也就是说,用户只能向channel_members中user_id等于当前会话变量x-hasura-user-id的频道发消息。
但问题来了:如果还要对message.content本身做复杂校验(例如调用外部内容审核服务判断是否违规),这类校验既写不进 Hasura 的权限布尔表达式,也无法用 Postgres 的 check 约束完成(check 约束只能引用当前行的列值,不能调用外部服务)。
1.2 矛盾点:为什么必须删掉 insert 权限
RFC 指出,在这种"权限 + 外部校验"并存的场景下,推荐的做法是使用Actions:开发者移除message表上user角色的 insert 权限,把全部校验逻辑搬进 action 的 webhook handler 里。
但这会带来一个明显的副作用:webhook 现在不仅要做 content 的校验,还必须自己重新实现{"channel": {"members": {"user_id": "x-hasura-user-id"}}}这一原本由 Hasura 权限系统负责的约束——等于把权限逻辑从声明式配置退化成 webhook 里的命令式代码,Hasura 权限系统的可复用性就此丢失。
那么,为什么必须移除 insert 权限,而不能"权限和 action 并存"?RFC 给出了关键原因:
如果 insert 权限被定义,
insert_message变更就会被自动生成,任何持有user角色的人都能直接调用它,从而绕过对message内容的校验。
也就是说,只要user角色对message表有 insert 权限,GraphQL schema 的mutation_root上就会暴露insert_message字段,客户端可以绕过 action 直接插入数据,外部校验形同虚设。这正是"复用 insert 权限于 action"这一需求的原始动机。该问题最初在 Hasura 官方 Discord 频道被报告。
二、RFC 提案:insert 权限新增admin_only字段
2.1 提案语义
RFC 提出的解决方案是在 insert permission 中引入一个新字段admin_only:
- 行为一:当某角色对该表的 insert 权限设置了
admin_only时,mutation_root上不再为该角色生成该表的 insert 变更字段,普通客户端无法直接调用; - 行为二:但该 insert 变更在携带
admin-secret的请求中仍然可用; - 行为三:action 的 handler 可以在调用 GraphQL Engine 时带上
admin-secret,并把x-hasura-role设置为用户的真实角色,从而以该角色身份执行 insert,使 insert 权限中定义的角色级约束(check 条件)照常生效——权限校验由 Engine 完成,webhook 只需专注内容校验。
2.2 与 GraphiQL"角色模拟"机制的关系
RFC 特别指出:当前 Engine 已经支持通过admin-secret+x-hasura-role在 GraphiQL 中模拟某个角色发起请求。因此admin_only的 insert 变更将同样允许"以admin-secret认证、以指定角色身份执行"的请求通过。
这意味着实现该提案时,需要在文档中明确说明:admin_secret配合x-hasura-role可以访问admin_only的插入变更,这是有意为之的信任模型——只有持有admin-secret的后端(如 action webhook)才能以受约束的角色身份执行插入,普通角色直接访问该字段则会被拒绝。
2.3 设计权衡
从 RFC 的表述可以提炼出该方案的核心权衡:
| 维度 | 说明 |
|---|---|
| 安全性 | admin_only从 schema 层面隐藏了角色的 insert 字段,避免绕过外部校验 |
| 权限复用 | 通过admin-secret+x-hasura-role继续使用声明式的角色 check 约束,webhook 无需重复实现权限逻辑 |
| 信任边界 | 约束的正确性依赖admin-secret只被可信后端(webhook)持有 |
| 文档义务 | 需要向开发者说明admin_only变更在 GraphiQL 角色模拟场景下同样可执行 |
三、仓库中的演进:从 RFC 到输入验证(Input Validations)落地
3.1 当前仓库中的实现现状
需要说明的是:在 graphql-engine 当前仓库的源码与文档中,admin_only字段并未以该名字落地(在server/src-lib与docs中均未检索到admin_only)。从源码演进看,该 RFC 所提出的"insert 前执行外部 webhook 校验"诉求,最终由输入验证(Input Validations)机制在 v2.29.0 起落地实现。
该机制允许在 insert/update/delete 权限上挂载validate_input配置,把变更的输入参数在真正写库前路由到一个 HTTP webhook 做校验。其官方文档位于 docs/docs/schema/postgres/input-validations.mdx。
3.2 与 RFC 方案的关系
RFC 与落地机制解决的是同一类问题(复杂数据校验),但路径不同:
- RFC 方案(admin_only):把"直接 insert"从角色 schema 中隐藏,将插入委托给带
admin-secret的 action handler,让 Engine 代为执行角色约束——校验与插入分离在 action 的 webhook 与 Engine 两端; - 落地机制(validate_input):把校验 webhook 直接挂在权限上,由 Engine 在变更执行前先调用校验 webhook,校验通过后才开启数据库事务写入——校验与插入统一由 Engine 编排,无需隐藏 insert 字段,也就不需要
admin_only的 schema 隐藏语义。
两者都需要一个共识前提:webhook 校验失败时请求被中止并返回错误。RFC 中"必须移除 insert 权限否则可被绕过"的担忧,在validate_input落地后由 Engine 的执行流程直接接管,无需再移除权限。
3.3 输入验证的配置方式(实战)
以 Postgres 为例,在 CLI 管理的 Metadata 文件metadata -> databases -> [database-name] -> tables -> [table-name].yaml中,可以这样为一个角色的 insert 权限挂上校验 webhook:
- table: schema: public name: products insert_permissions: - role: user permission: columns: [] filter: {} validate_input: type: http definition: url: http://www.somedomain.com/validateProducts headers: - name: X-Validate-Input-API-Key value_from_env: VALIDATION_HOOK_API_KEY forward_client_headers: true timeout: 5各字段含义:
type:校验接口类型,当前支持http(webhook URL);definition.url:必填,校验 webhook 的 URL;definition.headers:可选,随请求发送的自定义头,value_from_env支持从环境变量取值;definition.forward_client_headers:是否把客户端请求头转发给校验 webhook;definition.timeout:webhook 超时时间(秒),可理解为该配置项的核心调优参数。
应用 Metadata:
hasura metadata apply也可以通过 Metadata API 创建(对应pg_create_(insert|update|delete)_permission,见 server/src-lib/Hasura/RQL/DDL/Permission.hs 中buildInsPermInfo/buildUpdPermInfo对validateInput的解析):
POST /v1/metadata HTTP/1.1 Content-Type: application/json X-Hasura-Role: admin { "type": "pg_create_insert_permission", "args": { "source": "<db_name>", "table": "products", "role": "user", "permission": { "columns": "*", "filter": {}, "validate_input": { "type": "http", "definition": { "url": "http://www.somedomain.com/validateProducts" } } } } }3.4 执行流程
官方文档明确了带校验的 insert 的执行顺序(摘自 docs/docs/schema/postgres/input-validations.mdx 的流程说明):
- 变更输入参数会先被发送到校验 webhook(对所有涉及的表都会执行);
- 数据库事务只在所有校验成功完成后才开始;
- 任一 webhook 拒绝数据,请求被中止并返回错误信息。
这意味着校验 webhook 是写入路径上的强制门禁——与 RFC 中"action webhook 负责校验、Engine 负责权限"的分工相比,validate_input把两者统一在 Engine 的变更执行管线中。文档同时提示:涉及校验的变更可能比无校验变更更慢,webhook 执行时间是潜在瓶颈(受timeout限制)。
四、仓库中的测试验证:端到端校验 webhook
仓库的 API 测试套件中有与该主题直接对应的端到端测试:server/lib/api-tests/src/Test/Mutations/Insert/ValidationSpec.hs。
该测试在 Postgres、Citus、CockroachDB 三个后端上运行同一套用例,测试的建表与权限结构如下:
user表(id / name / email / phone_number),角色user_1拥有 insert 权限,并挂载/validateUser校验 webhook;tweet表(id / content / user_id),角色user_1拥有 insert 权限,并挂载/validateTweet校验 webhook。
测试用例覆盖的校验行为包括:
| 用例 | 校验规则 | 期望错误 |
|---|---|---|
| 邮箱格式错误 | email 必须是合法邮箱 | Invalid email id "random email" |
| 手机号格式错误 | phone_number 必须合法 | Invalid phone number "987654321" |
| 关联插入超过限制 | 一个用户最多关联 1 条 tweet | Only one tweet is allowed to be added with a user |
| tweet 内容超长 | content 不超过 30 字符 | Tweet should not contain more than 30 characters |
测试通过携带X-Hasura-Role: user_1请求头发起变更,断言返回code: validation-failed错误,证明校验 webhook 在权限生效的同时被强制执行。
承载这些校验的测试 webhook 实现在 server/lib/test-harness/src/Harness/Webhook.hs 中,其runInputValidationWebhook起了一个本地 Spock 服务,提供/validateUser与/validateTweet两个校验端点,从请求体$rows中提取数据后逐条执行校验逻辑,任一失败即返回校验错误。这一实现恰好演示了 RFC 中"action 的 handler / 校验 webhook 需要做内容级校验"的最小落地形态。
五、admin-secret+x-hasura-role:角色模拟的官方语义
RFC 方案成立的关键前提是"admin-secret+x-hasura-role可模拟指定角色执行请求"。这一机制在官方文档 docs/docs/auth/authentication/admin-secret-access.mdx 中有明确记载:
如果请求同时携带
X-Hasura-Admin-Secret头,以及X-Hasura-Role等用户特定头(如X-Hasura-User-Id),Hasura GraphQL Engine 将使用该用户与角色对应的访问控制规则处理请求,而不是以 admin 身份处理。
这正是 RFC 中admin_only方案的核心依据:action 的 handler(持有admin-secret)可以"扮演"任意角色发起插入,从而让该角色在 insert 权限里声明的 check 约束(例如频道成员关系)由 Engine 强制执行。
官方文档同时给出了安全警示:admin-secret绝不能在面向用户的客户端暴露,否则恶意用户可以通过检查请求获取它——这与 RFC 方案中"admin_only的可达性依赖 admin-secret 只被可信后端持有"的信任模型完全一致。相关的会话变量传递机制(action 请求体中的session_variables,如x-hasura-user-id、x-hasura-role)可参见 docs/docs/actions/action-handlers.mdx 中关于 action 请求负载的说明。
六、方案对比与选型建议
综合 RFC 与仓库现状,可以把两类实现路径放在一起对比:
| 维度 | RFC 方案:admin_only+ action | 落地机制:validate_input输入验证 |
|---|---|---|
| 校验执行者 | action 的 webhook handler | 挂在权限上的校验 webhook(Engine 统一编排) |
| insert 字段暴露 | 角色 schema 中隐藏,仅admin-secret可访问 | 正常暴露,但写库前必经校验门禁 |
| 权限约束复用 | 依赖admin-secret+x-hasura-role角色模拟 | 权限 check 与校验 webhook 并存,无需移除权限 |
| 信任边界 | 要求admin-secret仅存于可信后端 | 校验 webhook 必须被正确配置且可达 |
| 落地状态 | RFC 提案(当前仓库未以该字段名实现) | 已实现,文档见 input-validations.mdx,测试见 ValidationSpec.hs |
选型建议:
- 如果你的场景只是"在插入/更新/删除前调用外部服务做内容校验",优先使用已经落地的
validate_input机制,它无需隐藏 insert 字段,权限与校验天然共存; - 如果你的场景是"用 action 承担完整的数据写入(含内容处理),但希望角色级约束仍由 Engine 声明式执行",RFC 的
admin_only思路依然有参考价值——它揭示了admin-secret+x-hasura-role角色模拟这一底层能力,可用于在 action handler 内以受约束角色身份执行插入; - 无论走哪条路,都要守住同一条底线:校验入口(webhook / action handler)与权限判定(Engine)不能被客户端绕过,这正是 RFC 最初强调"必须移除 insert 权限否则校验被绕过"的教训所在。
七、小结
reuse-insert-permission-in-action.md这份 RFC 记录了一个小而典型的工程问题:外部校验与声明式权限如何共存而不互相绕过。它提出的admin_only方案虽然未在当前仓库中以同名落地,但其问题分析、信任模型(admin-secret+x-hasura-role角色模拟)和"校验失败即中止"的执行语义,都直接反映在仓库现已实现的输入验证机制及其测试中。理解这份 RFC,有助于把握 Hasura 权限系统与 Actions 的分工边界,也能在遇到"权限表达不了 + check 约束做不到"的校验需求时,快速定位到正确的实现路径。
- 后端
- API网关
- 数据库
- GraphQL
【免费下载链接】graphql-engine
Blazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.
相关推荐
GraphQL Engine 输入校验 RFC 深度解析:用 validate_input Webhook 在 Mutation 落库前拦截非法数据
GraphQL Engine 输入校验 RFC 深度解析:用 validate_input Webhook 在 Mutation 落库前拦截非法数据 本文基于仓
后端API网关数据库GraphQLHasura GraphQL Engine 继承角色权限改进技术解析:从 RFC 到源码实现
Hasura GraphQL Engine 继承角色权限改进技术解析:从 RFC 到源码实现 本篇基于仓库中的技术规格文档 inherited roles im
后端API网关数据库GraphQLHasura GraphQL Engine 中 Computed Field 的过滤、权限与排序能力解析
Hasura GraphQL Engine 中 Computed Field 的过滤、权限与排序能力解析 本文基于 Hasura GraphQL Engine(
后端API网关数据库GraphQL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考