- 开发工具
- 后端
- API设计
【免费下载链接】graphql-playground
🎮 GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration)
graphql-playground-middleware-express 是 GraphQL Playground 项目提供的 Express 中间件,用于在 Express 应用中暴露一个路由,向浏览器返回 GraphQL Playground IDE 的完整 HTML 页面。本文以该中间件(仓库当前版本 1.7.22)为核心,完整覆盖其安装、最小接入、全部配置项、底层实现原理,以及官方重点提示的 XSS 反射漏洞修复与升级方案,帮助读者在自己的 Express GraphQL 服务上快速、安全地集成 Playground。
中间件定位:一行代码挂载 GraphQL IDE
GraphQL Playground 是一个图形化 GraphQL IDE(支持交互式文档、订阅与协作)。graphql-playground-middleware-express的作用是:把 Playground 的 HTML 页面(含样式、脚本与初始化配置)通过 Express 路由输出给浏览器。它本身不解析 GraphQL 请求,只负责"渲染 IDE 页面",真正的 GraphQL 端点由你的应用另行提供。源码中对它的类型定义也印证了这一点——它就是一个标准的 Express 请求处理器:
export type ExpressPlaygroundMiddleware = ( req: Request, res: Response, next: () => void, ) => void参见 packages/graphql-playground-middleware-express/src/index.ts。
安装
官方 README 同时提供了 yarn 与 npm 两种方式:
yarn add graphql-playground-middleware-express或:
npm install graphql-playground-middleware-express --save从仓库内 package.json 可以看到该包的实际依赖关系与运行前提:
- peerDependencies:
express: ^4.16.2,即要求宿主应用使用 Express 4.x(>= 4.16.2); - dependencies:
graphql-playground-html: ^1.6.29,页面渲染能力全部来自这个底层包; - 包体仅发布
dist目录,主入口为dist/index.js,类型声明为dist/index.d.ts。
最小接入示例
README 给出的最小用法如下:
const express = require('express') const expressPlayground = require('graphql-playground-middleware-express') .default const app = express() app.get('/playground', expressPlayground({ endpoint: '/graphql' }))注意两点:
- 必须取
.default:该包以 ES 模块风格导出,CommonJS 环境下需通过.default取到真正的中间件函数; endpoint指向你的 GraphQL 端点:Playground 页面加载后,所有查询/订阅请求都会发往该地址。
启动后访问http://localhost:<port>/playground即可打开 IDE。
完整可运行示例:结合 Apollo Server
仓库的 examples/basic/index.js 提供了一个完整的可运行示例——用apollo-server-express起一个 GraphQL 服务,再用本中间件暴露 Playground:
const express = require('express') const { ApolloServer, gql } = require('apollo-server-express') const expressPlayground = require('../../dist/index').default const typeDefs = gql` type Query { hello: String! } schema { query: Query } ` const resolvers = { Query: { hello: () => 'world', }, } const PORT = 4000 const server = new ApolloServer({ typeDefs, resolvers }) const app = express() server.applyMiddleware({ app }) app.get( '/playground', expressPlayground({ endpoint: '/graphql/</script><script>alert(1)</script><script>', }), ) app.listen(PORT) console.log( `Serving the GraphQL Playground on http://localhost:${PORT}/playground`, )运行方式见 examples/basic/README.md:
$ yarn $ node index.js该示例的endpoint故意写成了一段 XSS 载荷(</script><script>alert(1)</script>),这是官方用于验证与演示安全修复的用例——在已修复的版本中,这段输入会被清洗后安全输出,不会执行脚本。它恰好演示了下一节要讲的配置项与安全机制。
配置项详解(MiddlewareOptions)
中间件接收的唯一参数是一个 options 对象,其完整类型定义在底层包 packages/graphql-playground-html/src/render-playground-page.ts 中:
| 配置项 | 类型 | 说明 |
|---|---|---|
endpoint | string | GraphQL 端点地址,Playground 默认请求目标 |
subscriptionEndpoint | string | WebSocket 订阅端点;兼容旧写法subscriptionsEndpoint(会被自动转换并过滤) |
workspaceName | string | 工作区名称 |
env | any | 环境标识,传入'react'或'electron'时不注入 CDN 资源 |
config | any | GraphQL 配置(如 .graphqlconfig 内容),传入后会被序列化为configString注入页面 |
settings | Partial<ISettings> | IDE 初始设置(见下方设置表) |
schema | IntrospectionResult | 预置的 introspection 结果({ __schema: any }),可离线渲染文档 |
tabs | Tab[] | 预置的标签页,每个 Tab 含endpoint、query、name、variables、responses、headers |
codeTheme | EditorColours | 代码编辑器配色(属性、注释、关键字、字符串等各 token 颜色) |
其中settings支持的内置键(ISettings接口,见 render-playground-page.ts):
| 设置键 | 类型 / 取值 | 说明 |
|---|---|---|
general.betaUpdates | boolean | 是否启用 beta 更新 |
editor.cursorShape | 'line' \| 'block' \| 'underline' | 光标形状 |
editor.theme | 'dark' \| 'light' | 主题 |
editor.reuseHeaders | boolean | 是否跨请求复用请求头 |
tracing.hideTracingResponse | boolean | 是否隐藏 tracing 响应 |
tracing.tracingSupported | boolean | 是否支持 tracing |
editor.fontSize | number | 编辑器字号 |
editor.fontFamily | string | 编辑器字体 |
request.credentials | string | 请求凭证模式 |
request.globalHeaders | { [key: string]: string } | 全局请求头 |
schema.polling.enable | boolean | 是否启用 schema 轮询 |
schema.polling.endpointFilter | string | 轮询端点过滤 |
schema.polling.interval | number | 轮询间隔 |
底层原理:中间件如何生成 Playground 页面
中间件本体(express 侧)
packages/graphql-playground-middleware-express/src/index.ts 中的实现非常精简:
const express: Register = function voyagerExpress(options: MiddlewareOptions) { return (req, res, next) => { res.setHeader('Content-Type', 'text/html') const playground = renderPlaygroundPage(options) res.write(playground) res.end() } }流程即:设置Content-Type: text/html→ 调用graphql-playground-html的renderPlaygroundPage(options)生成完整 HTML → 写入响应并结束。注意next参数被保留但未调用,说明该中间件设计为路由终点。
页面渲染(html 侧)
renderPlaygroundPage的实现见 packages/graphql-playground-html/src/render-playground-page.ts,关键步骤:
兼容处理:旧字段
subscriptionsEndpoint会被转换为subscriptionEndpoint并过滤;传入config时序列化为configString。缺参告警:若
endpoint与configString都为空,会向控制台输出告警:WARNING: You didn't provide an endpoint and don't have a .graphqlconfig. Make sure you have at least one of them.注入配置:将完整 options 以 JSON 形式写入
<div id="playground-config">(该 div 默认display: none),页面加载后由GraphQLPlayground.init(root, JSON.parse(configText))读取并初始化 IDE。加载动画:页面中预置了来自 get-loading-markup.ts 的加载容器(含 SVG Logo 与一系列淡入/缩放动画),
window.onload后通过添加fadeOut类淡出。CDN 资源:默认从
//cdn.jsdelivr.net/npm拉取graphql-playground-react包的build/static/css/index.css与build/static/js/middleware.js;传入faviconUrl可自定义 favicon。
安全须知:XSS 反射漏洞(务必阅读)
官方 README 在显著位置给出了安全警告:在1.7.16之前的版本中,如果直接把未经清洗的用户输入传给expressPlayground(),存在安全漏洞。仓库的 SECURITY.md 与 docs/security/2020-xss-template-injection.md 记录了完整细节。
漏洞影响范围
漏洞根源在graphql-playground-html的renderPlaygroundPage,波及所有下游调用方。官方给出的受影响与修复版本对照:
graphql-playground-html:1.6.22 起安全graphql-playground-express:1.7.16 起安全graphql-playground-koa:1.6.15 起安全graphql-playground-hapi:1.6.13 起安全graphql-playground-lambda:1.7.17 起安全
漏洞本质是 XSS 反射攻击:攻击者可把恶意脚本注入endpoint、settings等任何会被渲染进 HTML 的参数,可能导致数据或凭证泄露、系统被破坏。
安全与不安全的写法对比
静态输入(所有版本都安全):
// expressPlayground 静态 endpoint app.get('/playground', (req) => expressPlayground({ endpoint: `/our/graphql`, settings: { 'editor.theme': req.query.darkMode ? 'dark' : 'light' }, }), )未清洗的用户输入(修复前有漏洞):
// endpoint 直接拼接路由参数 app.get('/playground/:id', (req) => expressPlayground({ endpoint: `/our/graphql/${req.params.id}`, }), ) // settings 直接取查询参数 app.get('/playground', (req) => expressPlayground({ endpoint: `/our/graphql`, settings: { 'editor.fontFamily': req.query.font }, }), )注意:不只是endpoint,任何来自用户的输入(如 settings 值)都可能成为注入点。
升级步骤
yarn:
yarn add graphql-playground-express@^1.7.16npm:
npm install --save graphql-playground-express@^1.7.16修复实现:源码如何清洗输入
当前版本renderPlaygroundPage在 render-playground-page.ts 中使用xss包对用户可控字段做统一过滤:
const filter = (val) => { return filterXSS(val, { whiteList: [], stripIgnoreTag: true, stripIgnoreTagBody: ["script"] }) }whiteList: []:不允许任何 HTML 标签白名单保留;stripIgnoreTag: true:剥离无法识别的标签;stripIgnoreTagBody: ["script"]:直接移除<script>标签的整个内容体。
endpoint、subscriptionEndpoint、favicon、CDN URL 等字段在渲染前都会经过filter(见 render-playground-page.ts),依赖xss@^1.0.6(见 packages/graphql-playground-html/package.json)。这正是前文示例中那段</script><script>alert(1)</script>载荷会被无害化的原因。
无法升级时的规避方案
官方建议:在应用层自行清洗用户输入,推荐使用与官方相同的xss包。例如:
const express = require('express') const { filterXSS } = require('xss') const expressPlayground = require('graphql-playground-middleware-express') .default const app = express() const filter = (val) => filterXSS(val, { whitelist: [], stripIgnoreTag: true, stripIgnoreTagBody: ['script'] }) // 简单示例:过滤路径参数 app.get('/playground/:id', (req) => expressPlayground({ endpoint: `/graphql/${filter(req.params.id)}` }) ) // 进阶示例:整体清洗 query 对象 app.get('/playground', (req) => expressPlayground(JSON.parse(filter(JSON.stringify(req.query)))) )仓库还提供了一份可运行的 XSS 攻击示例与 PoC,见 packages/graphql-playground-html/examples/xss-attack(含 README 与可执行脚本)。
最佳实践小结
- 版本:确保
graphql-playground-middleware-express >= 1.7.16(当前仓库版本为 1.7.22); - 永远不要把未清洗的请求参数(
req.params、req.query、req.body等)直接传入expressPlayground()的任意字段; endpoint与config至少提供其一,否则 Playground 页面会因缺少目标端点而无法正常工作(浏览器控制台会收到官方告警);- Playground 中间件只负责渲染 IDE,GraphQL 请求处理请交给你的 GraphQL 服务器(如 Apollo Server);
- 若需要自定义页面标题、favicon、预置标签页或主题,通过
title、faviconUrl、tabs、settings、codeTheme等扩展字段传入即可。
关联资源
- 中间件实现:packages/graphql-playground-middleware-express/src/index.ts
- 页面渲染与配置类型:packages/graphql-playground-html/src/render-playground-page.ts
- 页面入口导出:packages/graphql-playground-html/src/index.ts
- 加载动画 markup:packages/graphql-playground-html/src/get-loading-markup.ts
- 完整可运行示例:packages/graphql-playground-middleware-express/examples/basic/index.js
- XSS 漏洞详情:docs/security/2020-xss-template-injection.md
- 已知漏洞索引:SECURITY.md
- 开发工具
- 后端
- API设计
【免费下载链接】graphql-playground
🎮 GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration)
相关推荐
提升 WordPress 主题代码质量:PHPCS、ESLint 与 wp-scripts 完整工作流指南
提升 WordPress 主题代码质量:PHPCS、ESLint 与 wp scripts 完整工作流指南 WordPress 主题 _s(Underscore
后端前端VLA-Adapter Pro版本全面评测:从97.8%到99.6%的性能飞跃
VLA Adapter Pro版本全面评测:从97.8%到99.6%的性能飞跃 在人工智能与机器人技术飞速发展的今天,VLA Adapter Pro版本以其惊人
人工智能具身智能机器人微调在 Express 中集成 GraphQL Playground:基础示例、中间件原理与安全实践
在 Express 中集成 GraphQL Playground:基础示例、中间件原理与安全实践 GraphQL Playground 是面向 GraphQL
开发工具后端API设计
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考