### [瓜奇为什么是企业级安全](https://guaqi.com/docs/71324) **Published:** 2026-04-19T09:42:12 **Author:** 春哥 **Excerpt:** 安全是从架构层面就考虑的第一优先级。 WordPress 的安全问题是绕不开的坎。几乎每个用 WP 的人都中过招:要么被挂马,要么被挖矿,要么数据库被拖。WP 用的人多,漏洞也多,插件主题更是后门重灾区。 我见过太多企业站因为一个免费插件被黑,损失惨重。所以开发瓜奇的时候,**安全是从架构层面就考虑的第一优先级**。 * * * ## 传统 WordPress 的安全困局 ### 为什么 WP 总是被黑? **不是因为 WP 代码写得烂,而是因为 WP 太开放了。** 1. **插件生态是双刃剑** - 免费插件可能藏着后门 - 小众插件长期不更新,漏洞没人修 - 插件之间可能有冲突,暴露安全漏洞 1. **主题也可能是雷** - 来路不明的主题藏着一堆恶意代码 - 就算付费主题,也不敢保证 100% 安全 1. **WP 本身太暴露** - 后台地址 /wp-admin 地球人都知道 - 登录接口就在那里,随便被扫 - xmlrpc.php 曾经是 DDoS 工具 - 各种 REST API 接口暴露给外面 **结果是啥?你的站点就像一个装满宝藏的房子,但门锁大家都知道,钥匙在兜里揣着。** * * * ## 瓜奇的安全架构 ### 核心理念:把 WordPress 藏起来 瓜奇的做法很简单:**让外部根本接触不到 WordPress**。 我们是怎么做的? * * * ### 1\. WP 后端完全屏蔽外部访问 在瓜奇的架构里,WordPress 后端可以完全屏蔽外部访问: **实际配置是这样的:** ``` # .env 文件 API_BASE=http://127.0.0.100/graphql API_SECRET=Ej0z5rZjL7Jsy3F5RkN552un59xxtrDa ``` 看到没?WP 的 GraphQL 地址是内网 IP `127.0.0.100`。 **这意味着什么?** - 外部网络根本访问不到你的 WP - 只有你的 Node 服务器能访问 - 黑客就算拿到了你的 WP 地址,也连不上 如果你的 Node 和 WP 在同一台服务器,甚至可以只给 WP 绑定一个 `127.0.0.1`,这样只有本机能访问。 * * * ### 2\. 不需要装主题和插件 传统 WordPress 再安全,也得装主题、装插件吧?只要装了第三方东西,就有风险。 **瓜奇不一样:** - WP 后端只需要装瓜奇插件 - 不需要装任何主题 - 不需要装其他插件 **安全好处:** - 攻击面降到最低 - 没有第三方代码漏洞 - 后门根本没地方藏 * * * ### 3\. Node 转发,完全隐藏 WP 地址 前端用户的请求是这样的: ``` 用户浏览器 → Node.js 服务器 → WordPress ``` **用户根本不知道 WP 在哪里。** 具体是怎么实现的? 在瓜奇的 Node 服务端代码里(`layers/base/server/api/request.js`),所有 GraphQL 请求都是通过 Node 转发的: ``` // 用户的浏览器请求 POST /api/request { "action": "posts.getPost", "data": { "id": 123 } } // Node 服务端处理 // 1. 根据 action 找到对应的 GraphQL 查询模板 // 2. 用预定义的查询字符串 + 用户数据 // 3. 发请求到 WP(内网地址) // 4. 返回结果给用户 // 用户看到的响应: { "data": { ... } } ``` **关键点:** - 用户的浏览器从来不会直接请求 WP - WP 的地址、接口路径,用户完全看不到 - 就算用户打开浏览器开发者工具,看到的也只是 `/api/request` 这个 Node 接口 * * * ### 4\. GraphQL 查询字符串后端预定义 这个是瓜奇安全的核心中的核心。 **传统做法(不安全):** ``` // 前端直接传 GraphQL 查询 const query = ` query { posts { nodes { id title content // 可能包含敏感信息 } } } `; fetch('/graphql', { method: 'POST', body: JSON.stringify({ query }) }); ``` **问题在哪?** - 前端可以随便构造查询 - 可能请求到不该看的数据 - 可能构造恶意查询拖库 **瓜奇的做法(安全):** ``` // 前端只能传 action const response = await fetch('/api/request', { method: 'POST', body: JSON.stringify({ action: 'posts.getPost', // 只能调用预定义的操作 data: { id: 123 } // 只能传参数 }) }); ``` Node 服务端代码(`layers/base/server/api/request.js`): ``` // 根据 action 找到对应的 GraphQL 查询模板 const queryFn = query(event); const str = resolvePath(queryFn, action); // 找不到预定义的查询?403! if (!str) { return { statusCode: 403, messages: ['action not found'], }; } // 用预定义的查询模板 + 用户数据 queryStr = await str(body.data); // 发请求到 WP res = await wpClient(config.private.apiBase, { body: { query: queryStr } }); ``` **安全优势:** - 前端不能随便传 GraphQL 查询字符串 - 所有查询都是后端预定义好的 - 查询模板里已经写死了要返回哪些字段 - 想查不该查的数据?没门 **实际效果:** ``` // 假设预定义的查询模板是这样的 queryStr = ` query getPost($id: ID!) { post(id: $id) { id title excerpt // 只返回摘要,不返回完整内容 } } `; // 用户想查完整内容?没门 // 用户想查用户表?没门 // 用户想拖库?更没门 ``` * * * ### 5\. API 地址只在服务端配置 看瓜奇的配置文件(`nuxt.config.ts`): ``` runtimeConfig: { public: { // 公开的配置,浏览器能看到的 }, private: { apiSecret: process.env.API_SECRET, apiBase: process.env.API_BASE, // WP GraphQL 地址 // ... 其他敏感配置 }, } ``` **关键点:** - `apiBase` 放在 `private` 里,只有服务端能访问 - 浏览器(前端代码)根本拿不到这个配置 - 就算用户查看网页源代码,也看不到 WP 的地址 **传统做法的问题:** ``` // 很多前端项目会这样写 const API_URL = 'https://api.example.com/graphql'; // 暴露在前端代码里 // 用户打开浏览器开发者工具,一眼就看到了 // 甚至可以直接调这个接口 ``` **瓜奇的做法:** ``` // 前端代码里没有 WP 地址 const response = await $fetch('/api/request', { // 只调 Node 接口 method: 'POST', body: { action: 'posts.getPost', data: { id } } }); // WP 地址在哪?在服务端的 runtimeConfig.private 里 // 前端拿不到,浏览器看不到 ``` * * * ### 6\. 生产环境禁止 ad-hoc 查询 看这段代码(`layers/base/server/api/request.js` 第 29-30 行): ``` /** 生产环境禁止未映射的 queryStr;开发环境可传 queryStr 调试 */ const allowAdHocGraphql = import.meta.dev; ``` **意思是啥?** - 开发环境:允许前端传 queryStr,方便调试 - 生产环境:禁止!只能用预定义的查询模板 ``` // 第 86-91 行 if (!str && !(allowAdHocGraphql && body.queryStr)) { return { statusCode: 403, messages: ['action not found'], }; } ``` **实际效果:** ``` // 生产环境,前端想这样搞? fetch('/api/request', { method: 'POST', body: { queryStr: 'query { users { nodes { email password } } }' // 想拖用户表? } }); // 返回:403 action not found // 没门! ``` * * * ## 瓜奇的安全层级总结 ### 第一层:网络隔离 - WP 用内网 IP,外部访问不到 - Node 作为唯一入口,隐藏后端 ### 第二层:接口隔离 - 前端只能调 `/api/request` - WP 的 GraphQL 接口不暴露 - 用户根本不知道 WP 在哪 ### 第三层:查询隔离 - GraphQL 查询都是后端预定义的 - 前端只能传 action 和参数 - 不能随便构造查询 ### 第四层:数据隔离 - 查询模板写死了返回哪些字段 - 不该看的数据根本查不到 - 就算想拖库也拖不了 ### 第五层:环境隔离 - 开发环境灵活,生产环境严格 - 生产环境禁止 ad-hoc 查询 - 所有敏感配置只在服务端 * * * ## 实际效果对比 ### 传统 WordPress 站点 ``` 攻击面: ├── /wp-admin(地球人都知道的后台地址) ├── /wp-login.php(登录接口) ├── /xmlrpc.php(曾经的安全漏洞) ├── /wp-json/(REST API) ├── 主题文件(可能的后门) ├── 插件目录(无数的漏洞) └── 第三方插件主题(不知道藏了什么) 结果:到处都是入口,防不胜防 ``` ### 瓜奇架构 ``` 攻击面: └── Node.js 服务器(唯一的入口) └── /api/request(唯一的接口) └── 只能传预定义的 action └── WP 在内网,访问不到 结果:几乎没有攻击面 ``` * * * ## 有多安全? 这么说吧: **传统 WordPress:** - 就像一个装满钱的家,门牌号公开,钥匙在门口地毯下 - 小偷路过都能试试 **加了安全插件的 WordPress:** - 换了把好锁,加了报警器 - 但小偷还是知道你住哪 **瓜奇:** - 把钱存进了银行金库 - 外面只有个 ATM(Node 服务器) - 你只能按预定义的按钮(action)取钱 - 金库(WP)在哪?根本找不到 - 就算找到了,门在内网,进不去 * * * ## 补充:其他安全细节 ### 1\. Authorization Token 处理 看代码第 173-181 行: ``` let authorizationHeader = event.context.authToken ? `${event.context.authToken}` : null; if (authorizationHeader && trimmedServerUrl === "") { Object.assign(opt.headers, { authorization: authorizationHeader, }); } ``` Token 是从服务端上下文拿的,不是前端传的。前端拿不到原始 Token,只能通过 Node 中转。 ### 2\. 错误处理 第 345-349 行: ``` if (res.errors) { const msg = res.errors[0].message.replace(/https?:\/\/[^\s]+/g, ""); return { statusCode: 403, messages: [msg], }; } ``` 错误信息会把 URL 地址抹掉,避免泄露内部路径。 ### 3\. 内存管理 代码里有很多地方专门清理大对象引用: ``` // 清理原始大对象引用,帮助 GC if (rawLoginDataStr && res?.data?.guaqi) { delete res.data.guaqi.loginData; } rawLoginDataStr = null; ``` 不只是性能考虑,也是为了减少内存中敏感数据的驻留时间。 * * * ## 总结 瓜奇的安全不是靠一个防火墙、一个插件,而是从架构层面彻底解决。 **核心思路:** 1. 不依赖插件减少攻击面 2. WP 后端内网隔离物理隔离 3. Node 转发隐藏实现细节 4. 预定义查询防止恶意请求 5. 服务端配置避免信息泄露 **结果:** - 用户看不到 WP 地址 - 黑客找不到攻击入口 - 就算有漏洞,也打不到 WP 上 这不是传统 WordPress 的”加固版”,而是重新定义了 WordPress 的安全架构。 **企业级安全,不是靠堆砌安全插件,而是从架构设计时就考虑安全。瓜奇就是这么做的。** ---