Prisma GraphQL结合GraphQL Shield的JWT登录系统问题求助
嘿,刚好我之前也折腾过GraphQL Shield + Prisma的登录流程,你的两个问题我大概能猜到原因,给你分析下:
首先得明确Prisma里db.user()(单个用户查询)的参数要求:它接受的是唯一约束条件,也就是你在Prisma Schema里标记了@unique的字段,或者复合唯一索引。比如如果你的User模型里只有email @unique,那单独传email是符合UserWhereUniqueInput要求的,但同时传email和password就不行——因为这俩组合不是唯一约束,Prisma不知道怎么用这个条件定位单个唯一用户。
那为什么单个password能运行?可能你刚好给password也加了@unique?但这显然不合理,用户密码重复太正常了。
解决方法很简单:如果要用多个非唯一条件查询,应该用列表查询db.users()(Prisma 1)或者prisma.user.findFirst()(Prisma 2+),然后取第一个匹配的结果。举个例子:
// Prisma 1写法 Mutation: { login: (_, data, ctx) => { return ctx.db.users({ where: { email: ctx.request.body.variables.email, password: ctx.request.body.variables.password } }) .then(users => { // 取第一个匹配的用户 const user = users[0]; console.log(user); // 这里可以继续生成JWT等操作 return user; }) .catch(err => { console.error(err); return err; }) }, ... } // 如果是Prisma 2+,更推荐用findFirst Mutation: { login: async (_, { email, password }, ctx) => { try { const user = await ctx.prisma.user.findFirst({ where: { email, password } }); console.log(user); return user; } catch (err) { console.error(err); throw err; } } }
如果非要用db.user()的话,你得在Prisma Schema里给email和password加复合唯一约束,但真的不建议这么做,完全没必要。
ctx.db.query.user时被GraphQL Shield拦截 首先要搞清楚:GraphQL Shield管的是客户端发送的GraphQL请求字段,而不是你服务端内部调用Prisma的方法。你配置了Mutation: { login: allow },那客户端发送mutation { login(...) { ... } }的时候,Shield会放行这个请求;而你在login解析器里调用ctx.db.query.user,这是服务端和Prisma的交互,Shield根本管不着。
那为什么会报「Not Authorised!」?大概率是这几个原因:
- Shield默认规则是deny,但你没正确开放Mutation.login:比如拼写错误?比如你的GraphQL Schema里Mutation字段是
Login(首字母大写),但Shield里写的是login,大小写不匹配就会触发默认的deny规则。 - Shield的应用顺序不对:比如你把Shield middleware放在了其他权限校验之后,或者没正确挂载到Apollo Server上。比如在Apollo Server 3+里,应该用
plugins配置Shield,而不是旧版的middleware。 - 你混淆了Prisma的API版本:
ctx.db.query.user是Prisma 1的写法,如果你用的是Prisma 2+,这个API已经废弃了,换成prisma.user.findUnique()才对,旧API可能触发一些奇怪的权限问题。
给你一个正确的Shield配置示例(适配Prisma 2+和Apollo Server 3+):
// 定义Shield规则 const permissions = shield({ Query: { user: allow, // 客户端调用query { user(...) }会被放行 }, Mutation: { login: allow, // 客户端调用mutation { login(...) }会被放行 }, fallbackRule: deny, // 其他未配置的字段默认拒绝访问 }); // 配置Apollo Server const server = new ApolloServer({ typeDefs, // 你的GraphQL Schema resolvers, // 你的解析器 context: ({ req }) => ({ req, prisma: new PrismaClient() // 或者全局实例 }), plugins: [shield(permissions)], // 正确挂载Shield插件 });
另外,建议你不要直接从ctx.request.body.variables取参数,解析器的第二个参数data(或者解构的{ email, password })已经包含了客户端传递的变量,直接用更规范。
内容的提问来源于stack exchange,提问作者Rip3rs

