Prisma 认证与授权迁移指南:从 Graphcool Framework 迁移 Authentication 到应用层
2026/9/24 11:35:43 网站建设 项目流程
  • 后端
  • 数据库
  • GraphQL

【免费下载链接】prisma1

💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB) [deprecated]

项目地址:https://gitcode.com/gh_mirrors/pr/prisma1
点击查看免费下载

本指南讲解如何将原有 Graphcool Framework 服务中的认证(Authentication)与授权(Authorization)功能迁移到 Prisma 架构:Prisma 不再把用户认证与权限系统绑定,而是提供基于 JWT(Service Token)的简单令牌机制,用户注册/登录与数据访问权限(如"只有作者才能更新自己的 Post")全部下沉到 GraphQL 服务器的应用层实现。读完本文,你将掌握签名与校验 Prisma 服务令牌、用prisma-bindingexists函数替代 Graphcool 权限查询(permission queries),以及用graphql-yoga+bcryptjs+jsonwebtoken完整落地 signup / login / 权限校验的实战方案。

背景:Graphcool Framework 的认证与授权方式

在 Graphcool Framework 中,认证功能通过 resolver functions(解析器函数)实现:对 API 的 GraphQL schema 做扩展,追加提供注册(signup)和登录(login)能力的专用 mutation。整个过程通常包含三个步骤:

  1. 在 GraphQL schema 的Mutation类型上以 schema extension 的形式定义signup/loginresolver 函数;
  2. 直接在 Graphcool Framework 中以 JavaScript 提供这些 resolver 函数的实现,或使用 webhook 调用你自行托管的函数;
  3. 在 service 定义文件中把 mutation 定义与实现连接起来。

而在数据访问的安全控制方面,Graphcool Framework 使用permission queries(权限查询)概念:每个 API 操作都可以关联一条(或多条)权限规则,在执行操作前先进行校验。

这种把认证与权限都耦合进框架的方式,带来的架构问题是认证逻辑、业务逻辑与数据层界限模糊。Prisma 对此做了彻底调整。

Prisma 的认证概念:为什么功能被移入应用层

Prisma 的 authentication 概念与 Graphcool Framework 截然不同:

  • Prisma 不提供用户级认证。它只提供一个简单的、基于令牌(可以理解为API Key)的系统用于访问 Prisma API——即 service secret 与 service token 机制;
  • 用户认证与权限规则移入应用层,即你的 GraphQL 服务器(graphql-yoga等)负责实现;
  • prisma-binding包提供的exists函数在应用层扮演与 Graphcool Framework 中权限查询类似的角色;
  • JWT 令牌改由你自己生成,而不再由graphcool-lib代为生成。

这一设计对应迁移指南 Overview 中描述的两层架构:Prisma 提供数据库层(自动生成、面向 CRUD 的 GraphQL API),你的 GraphQL 服务器构成应用层(面向客户端、承载业务逻辑、认证与权限)。prisma-binding负责把应用层的请求转发给 Prisma API。

理解 Prisma 的服务密钥与令牌机制

在动手迁移之前,先理解 Prisma 自身的认证基础。Prisma 服务的 GraphQL API 默认由 service secret 保护,它定义在prisma.yml中:

service: my-service stage: ${env:PRISMA_STAGE} cluster: ${env:PRISMA_CLUSTER} datamodel: database/datamodel.graphql secret: ${env:PRISMA_SECRET}

secret的约束:必须是 UTF-8 编码、不能包含空格、最长 256 字符;也可以在单个字符串中以逗号分隔多个 secret(空格会被忽略),从而实现无缝密钥轮换

secret: myFirstSecret, SECRET_NUMBER_2,3rd-secret

如果设置disableAuth: true,则任何人都拥有数据库的完整读写权限(默认是false,即默认启用认证)。

secret用于签发 JWT 服务令牌,令牌放在 HTTP 请求的Authorization头中:

Authorization: Bearer __TOKEN__

JWT 载荷(payload)包含两类必须的 claims:

{ "exp": 1300819380, "service": "my-service@prod" }
  • exp:令牌过期时间;
  • service:服务的名称与 stage。

Prisma 校验请求时会检查:令牌必须用服务配置的 secret 签名、exp必须指向未来、serviceclaim 必须与当前请求的服务及 stage 匹配。prisma.yml的完整字段定义可参考 YAML-Structure。

在 Node 端,可以基于jsonwebtoken库自行签发这样的服务令牌(这里的PRISMA_SECRET/PRISMA_STAGE来自环境变量):

var jwt = require('jsonwebtoken') jwt.sign( { data: { service: 'my-service@' + process.env.PRISMA_STAGE, }, }, process.env.PRISMA_SECRET, { expiresIn: '1h', } )

也可以直接用 CLI 命令prisma token获取当前 Prisma 服务的新签名 JWT,支持--copy复制到剪贴板与--env-file指定环境变量文件,详见 prisma-token:

prisma token prisma token --copy

在源码层面,prisma-client-lib的 Client 构造函数会在传入secret时自动用它签发空载荷令牌sign({}, secret),并把它作为Authorization: Bearer <token>头附加到对 Prisma API 的所有请求(包括订阅的connectionParams)。也就是说,应用层服务器与 Prisma 之间的服务级认证是自动完成的,你在应用层要操心的是你的用户与你的 GraphQL 服务器之间的用户认证。

迁移前提

以下迁移步骤假设:

  • 你使用graphql-yoga作为 GraphQL 服务器;
  • 你的 schema 定义使用 SDL(Schema Definition Language)编写。

想直接看现成实现,可参考本仓库examples/目录下的示例(仓库中examples目录目前只包含说明文件,完整的 auth / permissions 示例在 Prisma 官方示例仓库中)。另有配套教程 Permissions 详述权限规则的实现。

迁移 Step 1:迁移 Schema 定义

假设你的 Graphcool Framework 服务中曾定义过如下 schema extension:

type Mutation { signupUser(email: String!, password: String!): SignupUserPayload authenticateUser(email: String!, password: String!): AuthenticateUserPayload } type SignupUserPayload{ userId: ID! token: String! } type AuthenticateUserPayload { token: String! }

其中signupUserauthenticateUsermutation 用于注册和登录。返回的token是由graphcool-lib生成的 JSON Web Token,客户端把它放进AuthorizationHTTP 头即可对 API 发起认证请求。

迁移时,把这些定义挪进你的graphql-yoga服务器的 schema 定义即可。这里还有个额外的好处:Graphcool 时期由于 resolver 函数无法返回模型类型(model types)而被迫做的 workaround 也可以一并去掉。例如新定义可以简化为:

type Mutation { signup(email: String!, password: String!): AuthPayload login(email: String!, password: String!): AuthPayload } type AuthPayload { token: String! user: User! }

AuthPayload直接返回User对象,schema 更干净,也更能表达业务语义。

迁移 Step 2:迁移 Resolver 函数

接下来为上面定义的signuploginmutation 实现 resolver。核心变化是:JWT 由你自己用jsonwebtoken生成,密码哈希用bcryptjssignupresolver 中还负责创建新的User节点。

auth.js

const bcrypt = require('bcryptjs') const jwt = require('jsonwebtoken') const auth = { async signup(parent, args, ctx, info) { const password = await bcrypt.hash(args.password, 10) const user = await ctx.db.mutation.createUser({ data: { ...args, password }, }) return { token: jwt.sign({ userId: user.id }, process.env.JWT_SECRET), user, } }, async login(parent, { email, password }, ctx, info) { const user = await ctx.db.query.user({ where: { email } }) if (!user) { throw new Error(`No such user found for email: ${email}`) } const valid = await bcrypt.compare(password, user.password) if (!valid) { throw new Error('Invalid password') } return { token: jwt.sign({ userId: user.id }, process.env.JWT_SECRET), user, } }, } module.exports = { auth }

几点说明:

  • signupctx.db.mutation.createUser是经由prisma-binding生成的 Prisma mutation,data: { ...args, password }args包含emailpassword,其中密码已替换为 bcrypt 哈希后的值——绝不能把明文密码写进数据库
  • loginctx.db.query.user({ where: { email } })按唯一字段email查询用户,然后bcrypt.compare校验密码;
  • 两个 resolver 都用jwt.sign({ userId: user.id }, process.env.JWT_SECRET)生成用户令牌,这里的JWT_SECRET应用层的密钥,与 Prisma 的 service secret 不是一回事——它只用于签名"你是谁"的用户令牌。

AuthPayload.js(解析user字段,避免把整棵User对象直接暴露):

const AuthPayload = { user: async ({ user: { id } }, args, ctx, info) => { return ctx.db.query.user({ where: { id } }, info) }, } module.exports = { AuthPayload }

tokenuser解耦后,可以确保只查询客户端真正请求的User字段(info会被透传给 Prisma)。

如何识别当前请求用户

在上面login之后,所有受保护的 resolver 都要先从请求上下文恢复userId。常见做法是封装一个getUserId(ctx)工具:从ctx.requestAuthorization头中取出Bearer <token>,用jsonwebtoken.verify验签得到userId;验签失败则抛出"未认证"错误。这也是下方权限校验代码中getUserId(ctx)的语义基础——它在用户未认证时直接抛错。

迁移 Step 3:迁移权限规则

如前所述,prisma-bindingexists函数是迁移权限查询的首选工具。它生成的ctx.db.exists.<Type>(filter)会在数据库中检查是否存在满足条件的节点,返回布尔值,非常适合在 resolver 内做前置条件判断。

在源码层面,prisma-client-lib的 Client 类对外暴露$exists(即ctx.db.exists的底层实现),并在构造时通过this.buildExists()构建,按模型类型生成exists.Post(...)/exists.User(...)这样的方法。

场景:只允许作者更新自己的 Post

假设你的 Prisma 服务数据模型如下:

type User @model { id: ID! @unique name: String! posts: [Post!]! } type Post @model { id: ID! @unique title: String! author: User! }

在 Graphcool Framework 中,要表达"只有Post的作者能更新它",你需要给updatePostmutation 关联如下 permission query:

query ($user_id: ID!, $post_id: ID!) { SomePostExists(filter: { id: $post_id author: { id: $user_id } }) }

而在 Prisma 中,你需要在应用层的updatePostresolver 里完成同样的检查:

async function updatePost(parent, { id, title, text }, ctx, info) { // `getUserId` throws an error if the requesting user is not authenticated const userId = getUserId(ctx) // this expresses the same condition as the permission query above const requestingUserIsAuthor = await ctx.db.exists.Post({ id, author: { id: userId, }, }) // only if the condition is true, the post is actually updated if (requestingUserIsAuthor) { return await ctx.db.mutation.updatePost({ where: { id }, data: { title, text }, }, info) } throw new Error( 'Invalid permissions, you must be an admin or the author of a post to update it', ) }

注意对应关系:

Graphcool FrameworkPrisma 应用层
permission query(如SomePostExistsctx.db.exists.Post({ ... })
框架内自动执行权限检查resolver 内显式检查,通过后才执行ctx.db.mutation.*
$user_id由框架注入getUserId(ctx)从请求令牌解析
检查失败由框架返回错误检查失败抛Error,由 GraphQL 服务器统一返回

扩展:组合多个权限条件(作者或管理员)

exists不仅支持单条条件,还支持组合判断。比如"作者本人ADMIN用户才能读取某篇 Post":

async post(parent, { id }, ctx, info) { const userId = getUserId(ctx) const requestingUserIsAuthor = await ctx.db.exists.Post({ id, author: { id: userId, }, }) const requestingUserIsAdmin = await ctx.db.exists.User({ id: userId, role: 'ADMIN', }) if (requestingUserIsAdmin || requestingUserIsAuthor) { return ctx.db.query.post({ where: { id } }, info) } throw new Error( 'Invalid permissions, you must be an admin or the author of this post to retrieve it.', ) }

这里的关键是给User增加role字段(配合Role枚举:ADMIN/CUSTOMER,默认CUSTOMER),且rolepassword一样不要暴露在应用层 schema中,从而对客户端隐藏。更完整的可运行示例(draftspublishdeletePost等 resolver 的逐步改造)参见 Permissions 教程。

在 GraphQL Playground 中验证认证与权限

完成迁移后,可以用 GraphQL Playground 验证流程:

  1. 启动服务器(yarn start),打开http://localhost:4000
  2. 发送signupmutation 创建用户并返回token
    mutation { signup( email: "sarah@graph.cool" password: "graphql" name: "Sarah" ) { token } }
  3. 复制返回的token,在 Playground 左下角的 HTTP Headers 中以 JSON 形式设置为Authorization头(把__TOKEN__替换为真实令牌):
    { "Authorization": "__TOKEN__" }
  4. 之后所有请求都代表该用户发出,可以验证:用另一个用户登录后尝试发布 Sarah 的 draft,会得到Post not found or you're not the author之类的错误。

小结:从框架到 Prisma 的认证/授权心智模型

迁移完成后,你的架构变成了清晰的"双 GraphQL 层":

  • Prisma(数据库层):只认 service token(JWT,由 service secret 签发),提供纯粹的 CRUD 数据访问能力,不做任何用户级权限判断;
  • 你的 GraphQL 服务器(应用层):负责用户注册/登录、生成用户 JWT、解析当前请求用户(getUserId)、用ctx.db.exists实现细粒度权限校验,再把合法请求转发给 Prisma。

这套模式的收益是认证与授权逻辑完全由你掌控,schema 更简洁(AuthPayload直接返回user),权限规则用普通 JavaScript 表达、可组合可测试,同时 Prisma 侧的 API 可以保持通用、复用和可预测。

  • 后端
  • 数据库
  • GraphQL

【免费下载链接】prisma1

💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB) [deprecated]

项目地址:https://gitcode.com/gh_mirrors/pr/prisma1
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询