- 后端
- API网关
【免费下载链接】crystal
🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!
本篇指南围绕 PostGraphile v5 文档中的“默认角色”概念展开,讲解 PostgreSQL 角色体系的层级与登录权限、PostGraphile 中
authenticator角色的职责与安全边界(noinherit),以及如何通过preset.grafast.context中的pgSettings.role在请求级别动态切换角色。读完本文,你将掌握从建库授权、连接认证到请求级角色注入的完整链路,并能在自己的 PostGraphile 项目中落地一套“最小权限 + 按需提权”的数据库访问方案。
PostGraphile 充分使用了 PostgreSQL 的角色(role)机制:数据库端的所有权限控制都建立在角色之上,服务端则通过连接串指定的角色进入数据库,再在每次请求中按需切换到目标角色。理解这一模型,是正确配置 PostGraphile 权限体系的前提。
PostgreSQL 角色基础:角色、用户与权限
PostgreSQL 通过CREATE ROLE创建任意数量的角色,并通过GRANT将权限授予角色。权限形如“从post表执行 SELECT”“向person表插入行”等细粒度操作,例如:
grant select on post to reader; grant insert on person to editor;角色具有层级:角色可以“授予”给角色
PostgreSQL 角色是分层的——你可以把角色授予其他角色。例如有角色editor(可修改数据库中的数据)和角色admin,执行:
grant editor to admin;之后admin就拥有了editor的全部权限。同时,admin还能在会话中把自己切换(SET ROLE)为editor——这意味着切换后,本次会话中你将不再拥有admin的权限,而只拥有editor角色被授予的权限。这种“降权”能力正是 PostGraphile 权限模型的核心机制。
用户就是可登录的角色
在 PostgreSQL 中,“用户”(user)本质上就是可以登录(LOGIN)的角色。以下两条语句完全等价,都创建了一个可以登录的admin角色(即用户):
create role admin login; create user admin;而下面两条语句同样等价,创建了一个不能登录的角色:
create role editor; create user editor nologin;所谓“登录”,指的是该角色可以用于连接串的认证部分。以上面的角色为例,你可以使用postgres://admin@localhost/mydb建立连接,但postgres://editor@localhost/mydb会被拒绝。
PostGraphile 中的角色:authenticator 与角色切换
连接串中的 authenticator
PostGraphile 要求你在连接服务器时至少提供一个可登录的用户(角色)。这个角色写在连接串中,此后统称为authenticator(认证者)。典型启动方式:
postgraphile -c postgres://authenticator@localhost/mydbauthenticator拥有 PostGraphile 运行时可能需要的一切权限——其中最重要、也往往唯一需要的权限,就是切换到某些特定角色的能力。同时它应当被严格限制,只能“看到”被允许暴露的数据。
noinherit:更强的安全边界
authenticator可以通过授权获得切换到其他角色的能力,例如:
grant visitor to authenticator;如果在创建authenticator时加上noinherit选项:
create role authenticator login noinherit ...;那么authenticator自身不继承被授予角色的权限,必须显式执行set role to visitor;之类切换后才能真正拿到目标角色的权限。这构成了一道很好的安全边界:连接进程在默认状态下没有任何业务权限,只有显式切换后才拥有对应角色的能力。
从源码看,PostGraphile 的测试套件正是按这一模式组织的:在 kitchen-sink-permissions.sql 中,postgraphile_test_authenticator只被授予usage于各 schema(以及少数必要的枚举表查询权限),而postgraphile_test_visitor才拥有对c.person、a.post等业务表的select/insert/update/delete(且多为列级授权)。两者一“薄”一“厚”,正体现了“authenticator 仅负责切换,业务权限全部集中在业务角色”的设计意图。
在请求中触发角色切换:pgSettings.role
PostGraphile 中,角色切换通过role设置项触发,该项可以在preset.grafast.context回调中填充(详见 config/context.mdx)。一个完整的graphile.config.mjs示例:
import { PostGraphileAmberPreset } from "postgraphile/presets/amber"; export default { extends: [PostGraphileAmberPreset], grafast: { context(requestContext, args) { // Extract the "session user" from the request context, e.g. const user = requestContext.expressv4?.req?.user; // Start with the role we've already set it to - e.g. via the JWT plugin let role = args.contextValue.pgSettings?.role; // If this is unset: default to "visitor" if logged in, anonymous otherwise role ??= user ? "visitor" : "anonymous"; return { pgSettings: { // Override the role in the pgSettings ...args.contextValue.pgSettings, role, }, }; }, }, };这段配置的逻辑非常直观:
- 先从请求上下文(如 Express 中间件注入的
req.user)判断当前会话用户; - 读取已有的
pgSettings.role(例如 JWT 插件已经写入的值)作为起点; - 若未设置,则根据是否登录回退为
visitor(已登录)或anonymous(匿名); - 返回新的
pgSettings,其中的role覆盖旧值。
这样,每个 GraphQL 请求都会携带一个明确的数据库角色,PostGraphile 在执行查询前切换到该角色,从而实现“同一套服务、按登录态访问不同数据”的效果。
从源码看 pgSettings 的底层执行链路
pgSettings不止承载role,它是Record<string, string | undefined> | null形式的通用设置集合(见 executor.ts)。除了role之外,你还可以放入search_path、timezone等任意 Postgres GUC 设置。
在数据库执行层面,dataplan-pg 的适配器会把pgSettings中的键值对序列化后,通过set_config在事务内生效:源码 pg.ts 显示,只要存在非空的设置项,就会先执行begin(或savepoint),随后执行:
select set_config(el->>0, el->>1, true) from json_array_elements($1::json) el其中$1是JSON.stringify(pgSettingsEntries)得到的键值对数组。注意set_config的第三个参数为true,表示设置仅在当前事务内有效——请求结束后随事务提交/回滚自动恢复,不会污染连接池中其他请求的状态。若执行出错,则会回滚并重新抛出异常(见 pg.ts)。
与其他角色注入方式的配合
旧的 pgSettings 选项已被标记为废弃
在 PostGraphile v4 中,开发者通过pgSettings选项(DirectOrCallback<Request, Record<string, string>>)注入角色等设置。在 v5 中该选项仍保留兼容,但已被明确标记为@deprecated,注释直接指向新的做法:“Please use grafast.context 'pgSettings' key instead”(见 presets/v4.ts)。新项目应直接使用preset.grafast.context回调返回pgSettings。
JWT 插件如何写入 role
仓库中的PgLazyJWTPreset(见 presets/lazy-jwt.ts)展示了另一条角色注入路径:当请求头携带Authorization: Bearer <token>时,插件校验 JWT 后,若 claims 中存在role字段,会写入pgSettings.role;其余 claims 则以jwt.claims.<key>的形式写入pgSettings(键名需匹配^[a-z_][a-z0-9_]*$且长度不超过 52)。这也是示例代码中“Start with the role we've already set it to - e.g. via the JWT plugin”所引用的事实来源。
插件作者在注释中特别提醒:你应当自己在preset.grafast.context中处理 JWT,以便完全掌控校验规则,只把真正需要的值传给 Postgres(见 presets/lazy-jwt.ts)——这与本文示例中“先读取 JWT 写入的 role、再按需覆盖”的做法一脉相承。
最佳实践小结
- 最小权限的 authenticator:连接串中的角色只授予必要的
usage与切换权限,并配合noinherit使用,使其默认不持有任何业务权限; - 业务角色分离:为“访客”“匿名”“管理员”等语义分别建立
nologin角色,集中授予各自所需的数据权限; - 请求级角色注入:在
preset.grafast.context中根据会话身份决定pgSettings.role,让每个请求以最小必要权限执行数据库操作; - 避免连接串复用特权角色:不要在连接串中直接使用拥有全部权限的角色,否则任何请求都可能以最高权限访问数据库。
借助 PostgreSQL 原生的角色层级与SET ROLE机制,PostGraphile 得以在应用层实现精细、可审计的数据库访问控制——理解默认角色模型,是安全使用 PostGraphile 的第一步。
本文基于仓库中 default-role.md 文档整理,并结合 dataplan-pg 与 postgraphile 源码验证其实现细节。
- 后端
- API网关
【免费下载链接】crystal
🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!
相关推荐
Default Role(默认角色):PostGraphile 的 PostgreSQL 角色安全模型入门
Default Role(默认角色):PostGraphile 的 PostgreSQL 角色安全模型入门 <output文章 PostGraphile 默认角
后端API网关深入解析 PostGraphile 默认角色机制:authenticator 与 role 切换的完整实战指南
深入解析 PostGraphile 默认角色机制:authenticator 与 role 切换的完整实战指南 导读 PostGraphile 将 Postgr
后端API网关NocoBase 角色并集(Role Union)多角色权限合并机制详解
NocoBase 角色并集(Role Union)多角色权限合并机制详解 在 NocoBase 中,一个用户可以被赋予多个角色,而“角色并集”(Role Uni
低代码后端前端人工智能AI 应用工作流自动化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考