Node.js+GraphQL环境下JWT鉴权开放无权限路由方案问询
解决GraphQL单路径下的JWT鉴权区分问题
我之前也踩过这个坑!因为GraphQL只有一个入口路径,express-jwt的unless确实没法直接按路径区分哪些内容需要登录、哪些不需要。不过有两个靠谱的解决方案,看你更倾向哪种:
方案1:全局解析Token但不强制验证,在GraphQL解析器里控制权限
这是最符合GraphQL设计理念的做法——先让所有请求都能进入GraphQL层,然后在具体的查询/突变解析器里判断用户是否登录,决定是否允许访问。
步骤1:修改JWT中间件配置
把原来的强制验证改成仅解析Token、不要求必须携带,这样未登录用户的请求也不会被直接拦截:
// 关键添加credentialsRequired: false,没有Token也不会返回401 app.use(jwt({ secret: constants.JWT_SECRET, credentialsRequired: false })) app.use( graphqlEndpoint, bodyParser.json(), graphqlExpress(async req => { let user = null // 如果Token有效,req.user会存在,此时查询用户信息 if (req.user) { user = await knex('users').where('id', req.user.id).first() } return { schema, context: { user } } }) )
步骤2:在GraphQL解析器中添加权限检查
比如你的Schema里有无需登录的home查询,和需要登录的me查询,就可以在解析器里分别处理:
const resolvers = { Query: { // 无需登录的home查询,直接返回数据 home: () => { return { title: '欢迎访问首页', content: '这是无需登录就能看的内容' } }, // 需要登录的me查询,先检查context中的user是否存在 me: (parent, args, context) => { if (!context.user) { throw new Error('请先登录才能访问此内容') } return context.user } } }
这种方式的好处是灵活度极高,哪怕同一个查询里的不同字段需要不同权限,也能在字段解析器里单独控制。
方案2:自定义Express中间件,解析GraphQL查询内容后再决定是否鉴权
如果你更倾向在Express层提前拦截未授权请求,可以自定义一个中间件,先解析GraphQL的查询内容,判断是否包含需要鉴权的操作,再决定是否触发JWT验证。
简单版(字符串匹配)
适合操作名称比较固定的场景:
const checkGraphQLAuth = (req, res, next) => { // 只处理GraphQL的POST请求 if (req.path === graphqlEndpoint && req.method === 'POST') { const { query } = req.body // 定义需要鉴权的操作列表 const protectedOps = ['me', 'createPost', 'deleteComment'] // 检查查询中是否包含需要鉴权的操作 if (query && protectedOps.some(op => query.includes(op))) { // 触发JWT验证,验证失败会直接返回401 return jwt({ secret: constants.JWT_SECRET })(req, res, next) } } // 无需鉴权的请求直接放行 next() } // 替换原来的app.use(jwt(...)),改用自定义中间件 app.use(checkGraphQLAuth)
进阶版(解析GraphQL AST)
如果担心字符串匹配误判(比如查询内容里刚好包含操作名称的字符串),可以用GraphQL官方的工具解析查询的抽象语法树(AST):
const { parse, visit } = require('graphql') const checkGraphQLAuth = (req, res, next) => { if (req.path === graphqlEndpoint && req.method === 'POST') { const { query } = req.body if (!query) return next() const ast = parse(query) let requiresAuth = false // 遍历AST,精准检查是否有需要鉴权的操作 visit(ast, { Field(node) { const protectedOps = ['me', 'createPost', 'deleteComment'] if (protectedOps.includes(node.name.value)) { requiresAuth = true } } }) if (requiresAuth) { return jwt({ secret: constants.JWT_SECRET })(req, res, next) } } next() } app.use(checkGraphQLAuth)
这种方式的好处是能在Express层提前拦截无效请求,但缺点是如果一个查询里同时包含需要鉴权和不需要鉴权的操作,会直接要求登录,灵活性不如方案1。
总结
个人更推荐方案1,因为它更贴合GraphQL的生态,权限控制可以和业务逻辑紧密结合,后续维护和扩展也更方便。如果你的权限规则比较简单,方案2也能快速解决问题。
内容的提问来源于stack exchange,提问作者Martin Nordström
相关产品推荐
相关产品推荐

