WordPress 的安全问题是绕不开的坎。几乎每个用 WP 的人都中过招:要么被挂马,要么被挖矿,要么数据库被拖。WP 用的人多,漏洞也多,插件主题更是后门重灾区。
我见过太多企业站因为一个免费插件被黑,损失惨重。所以开发瓜奇的时候,安全是从架构层面就考虑的第一优先级。
传统 WordPress 的安全困局
为什么 WP 总是被黑?
不是因为 WP 代码写得烂,而是因为 WP 太开放了。
- 插件生态是双刃剑
- 免费插件可能藏着后门
- 小众插件长期不更新,漏洞没人修
- 插件之间可能有冲突,暴露安全漏洞
- 主题也可能是雷
- 来路不明的主题藏着一堆恶意代码
- 就算付费主题,也不敢保证 100% 安全
- 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;不只是性能考虑,也是为了减少内存中敏感数据的驻留时间。
总结
瓜奇的安全不是靠一个防火墙、一个插件,而是从架构层面彻底解决。
核心思路:
- 不依赖插件减少攻击面
- WP 后端内网隔离物理隔离
- Node 转发隐藏实现细节
- 预定义查询防止恶意请求
- 服务端配置避免信息泄露
结果:
- 用户看不到 WP 地址
- 黑客找不到攻击入口
- 就算有漏洞,也打不到 WP 上
这不是传统 WordPress 的”加固版”,而是重新定义了 WordPress 的安全架构。
企业级安全,不是靠堆砌安全插件,而是从架构设计时就考虑安全。瓜奇就是这么做的。