拓冰建站拓冰建站
首页 / 资讯中心 / 正文

从 Graphcool Framework 迁移认证与授权到 Prisma:JWT、权限检查与 prisma-binding 实战指南

后端数据库GraphQL【免费下载链接】prisma1 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL MongoDB) [deprecated]项目地址https://gitcode.com/gh_mirrors/pr/prisma1点击查看免费下载本指南以 Prisma 官方升级文档为主体系统讲解如何将 Graphcool Framework 中的用户认证signup / login与数据授权permission rules迁移到 Prisma。你将掌握 Prisma 基于secret与 JWT 的 API 认证模型、如何在graphql-yoga应用层实现注册/登录解析器以及如何使用prisma-binding的exists函数把旧的权限查询改写为应用层权限检查。Graphcool Framework 中的认证与授权是如何工作的在 Graphcool Framework 中认证功能通过 resolver functions解析器函数实现你需要先在 GraphQL schema 中为Mutation类型扩展出专门的signup与loginmutation再通过 JavaScript 直接实现解析器逻辑或借助 webhook 调用自托管函数最后在服务定义文件中把 mutation 定义与实现连接起来。数据访问的授权则依赖permission queries权限查询概念API 的每个操作都可以关联一条或多条权限规则在执行操作前先检查这些规则是否满足。这种设计把认证、授权逻辑与数据层耦合在一起带来了两个明显的工程问题认证与权限规则散落在服务定义、schema 扩展与函数实现三处职责边界模糊resolver functions 受限于无法返回模型类型model types迫使开发者使用额外的 workaround例如单独定义SignupUserPayload包装类型。Prisma 的认证模型secret、JWT 与 Authorization HeaderPrisma 采用了与 Graphcool Framework 完全不同的 authentication concept它不再把认证绑定到一套权限系统上而是只提供一个基于 token可理解为 API Key的简单机制来保护 Prisma API 的访问。用户认证与权限规则全部下沉到 GraphQL server 的应用层实现。具体而言Prisma service 的认证由prisma.yml中的secret配置驱动。根据 prisma.yml YAML 结构文档secret具有以下关键约束约束项要求编码必须是 UTF-8空格不允许包含空格最大长度不超过 256 个字符类型必须是字符串多 secret 用逗号分隔仍为单一字符串轮换支持在同一字符串中编码多个 secret实现平滑轮换典型配置如下# 单一 secret secret: moo4ahn3ahb4phein1eingaep # 多个 secret用于轮换 secret: myFirstSecret, SECRET_NUMBER_2,3rd-secret # 从环境变量读取 secret: ${env:MY_SECRET}secret被用来生成签名认证令牌JWT客户端必须在 HTTP 请求的Authorizationheader 中携带该令牌。若服务未配置secretPrisma API 将完全不需要认证——任何拿到endpoint的人都可以发送任意查询和 mutation从而读写数据库因此生产环境务必配置secret。这一机制在源码中可以得到印证在 prisma-client-lib 的 Client 构造函数 中客户端实例化时会使用sign({}, secret!)生成 token并将Authorization: Bearer token附加到 HTTP 请求头与 WebSocket 订阅的connectionParams中const token secret ? sign({}, secret!) : undefined this._client new BatchedGraphQLClient(endpoint, { headers: token ? { Authorization: Bearer ${token}, } : {}, }) this._subscriptionClient new SubscriptionClient( endpoint.replace(/^http/, ws), { connectionParams: { Authorization: Bearer ${token}, }, ... }, WS, )可以看到Prisma 自身只关心“调用方是否持有合法 token”而不关心用户是谁、能做什么——用户身份识别与业务权限判断完全交给应用层完成。迁移第一步迁移 Schema 定义在 Graphcool Framework 服务中你可能有如下 schema 扩展type Mutation { signupUser(email: String!, password: String!): SignupUserPayload authenticateUser(email: String!, password: String!): AuthenticateUserPayload } type SignupUserPayload{ userId: ID! token: String! } type AuthenticateUserPayload { token: String! }signupUser和authenticateUser分别用于注册与登录返回的token是由graphcool-lib生成的 JSON Web Token客户端将其放入Authorizationheader 来认证后续请求。迁移到 Prisma 后你需要把这些定义搬进graphql-yogaserver 自己的 schema 定义中假设使用graphql-yoga作为 GraphQL server且 schema 以 SDL 编写。同时可以去掉原先因 resolver functions 无法返回模型类型而被迫引入的 workaround——即不再需要SignupUserPayload这类仅包装token的中间类型现在AuthPayload可以直接携带User模型。迁移后的建议定义type Mutation { signup(email: String!, password: String!): AuthPayload login(email: String!, password: String!): AuthPayload } type AuthPayload { token: String! user: User! }迁移第二步实现 Resolver 函数并自行签发 JWT迁移到 Prisma 后一个重要的变化是JWT 令牌不再由graphcool-lib生成而是由你在应用层自己签发通常使用jsonwebtoken包并用bcryptjs对密码做哈希与比对。auth.js——实现signup与login两个解析器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 }要点说明signup中使用bcrypt.hash(password, 10)对密码加盐哈希10 为 cost factor随后通过ctx.db.mutation.createUser写入User节点——这是 prisma-binding 生成的委托解析器delegate resolver底层会翻译成对 Prisma service 的 HTTP 请求login先按 email 查询用户再用bcrypt.compare校验密码任一步失败都会抛出明确错误两个解析器都用jwt.sign({ userId: user.id }, process.env.JWT_SECRET)签发 tokenJWT_SECRET应通过环境变量注入避免硬编码。AuthPayload.js——为AuthPayload.user字段提供解析确保只按需选择字段const AuthPayload { user: async ({ user: { id } }, args, ctx, info) { return ctx.db.query.user({ where: { id } }, info) }, } module.exports { AuthPayload }注意这里把infoselection set透传给ctx.db.query.user保证查询只拉取客户端真正需要的字段避免不必要的数据库开销。迁移第三步将权限查询改写为 exists 调用如前所述Prisma 用prisma-binding包中的exists函数取代了 Graphcool Framework 的 permission queries。假设你的 Prisma service 定义如下数据模型type User model { id: ID! unique name: String! posts: [Post!]! } type Post model { id: ID! unique title: String! author: User! }在 Graphcool Framework 中若要表达“只有Post的author才能更新它”你需要给updatePostmutation 关联如下权限查询query ($user_id: ID!, $post_id: ID!) { SomePostExists(filter: { id: $post_id author: { id: $user_id } }) }在 Prisma 中这个检查被移入应用层——在updatePost对应的解析器内部完成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, ) }这段代码的权限语义与上面的SomePostExists权限查询完全等价ctx.db.exists.Post({ id, author: { id: userId } })检查“存在一个 id 匹配且作者为当前用户的 Post”。getUserId(ctx)负责从请求上下文中解析 JWT 并取出userId解析失败即抛出未认证错误然后只有在requestingUserIsAuthor为真时才真正执行更新否则抛出权限错误。exists并不是魔法——它在 prisma-binding 底层有明确的实现。在 Client.ts 的 buildExists 方法 中可以看到它对 schema 中每个类型自动生成一个exists.Type函数该函数以where对象为参数内部调用对应类型的列表查询plural field并判断结果数组长度是否大于 0private buildExists(): Exists { const queryType this._schema.getQueryType() if (!queryType) { return {} } if (queryType) { const types getTypesAndWhere(queryType) return types.reduce((acc, { type, pluralFieldName }) { const firstLetterLowercaseTypeName type[0].toLowerCase() type.slice(1) return { ...acc, [firstLetterLowercaseTypeName]: args { // TODO: when the fragment api is there, only add one field return thispluralFieldName.then(res { return res.length 0 }) }, } }, {}) } return {} }也就是说exists.Post({...})本质上执行的是query.posts({ where: {...} })并返回布尔结果。官方 API 文档 prisma-bindings API 也将其描述为exists暴露每个类型一个函数接受where对象、返回boolean用于快速判断数据库中是否存在满足条件的节点。迁移小结从框架内置到应用层职责能力Graphcool FrameworkPrisma注册 / 登录schema 扩展 mutation resolver functions webhook应用层解析器graphql-yogabcryptjsjsonwebtokenToken 签发graphcool-lib自动生成应用层用jwt.sign自行签发API 层认证与权限系统绑定仅secret驱动的 tokenAPI Key机制数据权限permission queries 声明式规则prisma-binding的exists函数在解析器中检查返回模型类型受 resolver 限制需 workaround 包装类型AuthPayload可直接返回User总结从 Graphcool Framework 迁移到 Prisma 的认证与授权本质上是把“框架内置的认证/权限机制”重构为“应用层职责”Prisma 只通过secret JWT 保护 API 端点本身而用户身份验证signup / login、密码哈希bcryptjs与业务权限判断exists 解析器内检查全部由你的 GraphQL server 负责。这套模式不仅职责更清晰还消除了 resolver functions 无法返回模型类型的限制使AuthPayload可以直接携带用户对象。想要进一步了解 Prisma 中权限规则的实现细节可以参考 Prisma-Bindings 参考文档 以及 服务配置文档 中对secret的完整说明。赞分享后端数据库GraphQL【免费下载链接】prisma1 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL MongoDB) [deprecated]项目地址https://gitcode.com/gh_mirrors/pr/prisma1点击查看免费下载相关推荐Prisma 迁移指南从 Graphcool Framework 到 Prisma 的认证与授权Authentication AuthorizationPrisma 迁移指南从 Graphcool Framework 到 Prisma 的认证与授权Authentication Authorization后端数据库GraphQLPrisma 认证与授权迁移指南从 Graphcool Framework 迁移 Authentication 到应用层Prisma 认证与授权迁移指南从 Graphcool Framework 迁移 Authentication 到应用层 本指南讲解如何将原有 Graphco后端数据库GraphQL从 Graphcool Framework 到 Prisma认证与授权Authentication Authorization迁移完整指南从 Graphcool Framework 到 Prisma认证与授权Authentication Authorization迁移完整指南 本篇指南以后端数据库GraphQL创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门