授权与缓存
Auth、Caching 与 Batching。
30 分钟
授权与缓存
这一课你将能做到
- 在 GraphQL 服务里实现「认证在入口、授权在字段」的两层模型
- 用 DataLoader 同时解决批处理与请求级缓存,并守住数据隔离红线
- 设计客户端缓存 + 持久化查询 + CDN 的多层缓存策略
为什么要学
REST 的权限是「一个端点一套规则」,中间件一把梭。GraphQL 只有一个端点,权限粒度天然落到字段上:Lesson.content 免费用户能看,Submission.judgeOutput 只有提交者能看。粒度变细意味着漏一处就是越权。同时,「一个端点」让 HTTP 层缓存几乎失效,缓存策略必须重新设计——这两件事是 GraphQL 从玩具走向生产的分水岭。
人话:REST 是大门设岗,GraphQL 是每个房间单独配钥匙。
先建立图像
两层安全模型:
请求 ──▶ ① 认证(Authentication):你是谁?
│ 网关/BFF 校验 JWT → 注入 context.currentUser
│
└──▶ ② 授权(Authorization):你能看这个字段吗?
每个敏感字段的 resolver 内判断:
- 对象级:这条 submission 是你的吗?
- 字段级:judgeOutput 只有 owner/admin 可见
缓存全景:
客户端归一化缓存(Apollo)
↑ miss
持久化查询 + CDN(GET 的匿名公开查询)
↑ miss
服务端:DataLoader 请求级缓存 / Redis 对象缓存 / HTTP 响应缓存
↑ miss
数据库
跟着做
步骤 1:认证——context 注入当前用户
// Yoga / Apollo 通用模式
const yoga = createYoga({
schema,
context: async ({ request }) => {
const token = request.headers.get('authorization')?.replace('Bearer ', '')
const user = token ? await verifyJwt(token) : null // 验证失败 = null,不抛错
return { currentUser: user, loaders: createLoaders() }
}
})
关键决策:未认证不等于拒绝。公开数据(课时标题)匿名也能看;需要登录的字段在 resolver 里判断。整个操作级别的拒绝(401)只用于「该 Schema 完全不开放匿名」的场景。
步骤 2:对象级授权——这条数据是不是你的
const resolvers = {
Query: {
submission: async (_p, { id }, { currentUser, services }) => {
const sub = await services.submission.findById(id)
if (!sub) return null
if (sub.userId !== currentUser?.id && currentUser?.role !== 'ADMIN') {
throw new GraphQLError('无权查看', {
extensions: { code: 'FORBIDDEN' } // 机器可读码,见规范课
})
}
return sub
}
}
}
步骤 3:字段级授权——同一对象的不同字段不同权限
const resolvers = {
Submission: {
code: (sub, _a, { currentUser }) =>
sub.userId === currentUser?.id ? sub.code : null, // 他人代码 → null
judgeOutput: (sub, _a, { currentUser }) => {
if (currentUser?.role === 'ADMIN') return sub.judgeOutput
if (sub.userId === currentUser?.id) return sub.judgeOutput
return null // 可空设计在此兜底
}
}
}