1. 产品概览
微信开放平台的「授权回调域」只能填写一个域名。当你有多个业务站点(例如
wx.is3.cn、www.example.com)都需要微信扫码登录时,
每个站点单独申请网站应用既麻烦,也无法共用用户体系。
本网关把微信 OAuth 流程收敛到一个固定域名上:业务站点只负责跳转和换票, 微信只认识网关这一个回调域,用户资料则由业务后端凭密钥安全获取。
生成 state,302 到网关 /oauth/authorize。
校验 client_id、项目状态与回调域名白名单,跳转微信扫码页。
网关用 code 换取用户资料并记录日志,生成一次性 ticket。
浏览器带 ticket 回到业务站,后端调 /api/user/info 换取用户。
2. 微信平台准备
- 登录 微信开放平台(open.weixin.qq.com),进入「管理中心 → 网站应用」。
- 创建或打开一个已通过审核的网站应用,记录 AppID 与 AppSecret。
- 在「授权回调域」中填写网关域名,例如
login.fps5.cn。 - 确认该网站应用已绑定开放平台账号,否则可能拿不到 unionid。
login.fps5.cn 即可,不要写 https://、
不要带端口和 /oauth/callback。微信侧的实际回调地址由网关自动拼接。
https://你的网关域名 可以正常访问。
3. 部署网关
3.1 环境要求
| 项目 | 要求 |
|---|---|
| PHP | 8.1 及以上(推荐 8.2) |
| 扩展 | pdo_sqlite、curl、mbstring、openssl |
| Web 服务器 | Nginx / Apache,站点根目录指向 public/ |
| 可写目录 | database/(SQLite 文件)、runtime/ |
| 数据库 | 无需安装,首次访问自动创建 SQLite 表结构 |
3.2 上传与伪静态
把项目上传到服务器,将网站运行目录设置为 public。Nginx 伪静态参考:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Apache 环境已自带 public/.htaccess,确认 mod_rewrite 已开启即可。
首次部署后请给 database/ 与 runtime/ 写权限。
4. 初始化与系统设置
- 浏览器访问
https://login.fps5.cn/admin/init,创建管理员账号与密码。 - 使用管理员账号登录
https://login.fps5.cn/admin。 - 进入「系统设置」,填写微信 AppID、AppSecret。
- 填写「网关地址」,例如
https://login.fps5.cn,保存后所有跳转都会使用该地址。 - 设置 ticket 有效期(默认 300 秒,建议 120–600 秒)。
- 生产环境务必关闭模拟模式,保存设置。
| 设置项 | 说明 |
|---|---|
| 微信 AppID | 开放平台网站应用的 AppID,用于拼接扫码授权地址。 |
| 微信 AppSecret | 仅保存在服务端,用于向微信换取 access_token,切勿泄露。 |
| 网关地址 | 对外访问根地址,如 https://login.fps5.cn,末尾不要带 /。 |
| ticket 有效期 | 业务方换取用户资料的时限,超时或使用一次后立即失效。 |
| 模拟模式 | 开启后不调用微信接口,生成本地模拟用户,仅用于本地联调。 |
5. 创建接入项目
普通用户:微信扫码登录后进入「用户后台 → 我的项目 → 申请项目」,填写项目名称、允许域名、默认回调地址并提交,等待管理员审核。
管理员:可进入「管理员后台 → 项目管理」直接新增项目,或对用户提交的项目执行「通过 / 拒绝 / 停用 / 删除」。
一个业务站点对应一个项目,主要字段如下:
| 字段 | 说明 |
|---|---|
| 项目名称 | 便于识别的名称,会显示在授权日志中。 |
| 允许域名 | 回调地址的域名白名单,多个用英文逗号分隔,支持 *.example.com 通配。 |
| 默认回调地址 | 发起授权时未传 redirect_url 时使用,必须是 http(s) 绝对地址。 |
| 审核状态 | 待审核 / 已通过 / 已拒绝,仅「已通过」的项目可用于授权。 |
| 状态 | 停用后该项目无法再发起授权,已发出的 ticket 不受影响。 |
保存后系统会生成一对凭证:
ak_************************
可公开,用于标识项目,出现在跳转地址中。
sk_****************************************
必须保密,只能保存在业务后端,用于换取用户资料。
6. 业务站接入
业务站只需要两个动作:把用户送到网关,以及在回调里用 ticket 换取用户资料。
6.1 第一步:跳转发起授权
业务站生成随机 state 并保存在 Session,然后 302 到网关:
// 生成并保存 state,防止 CSRF
session_start();
$state = bin2hex(random_bytes(16));
$_SESSION['wx_state'] = $state;
$query = http_build_query([
'client_id' => 'ak_你的client_id',
'redirect_url' => 'https://wx.is3.cn/callback.php',
'state' => $state,
]);
header('Location: https://login.fps5.cn/oauth/authorize?' . $query);
exit;
| 参数 | 必填 | 说明 |
|---|---|---|
client_id | 是 | 项目凭证中的 client_id。 |
redirect_url | 否 | 授权完成后的回跳地址,域名必须在项目白名单内;不传则使用项目默认回调地址。 |
state | 建议 | 业务方自定义随机串,网关会原样带回,用于校验请求来源。 |
6.2 第二步:回调换取用户
微信授权完成后,浏览器会回到你的 redirect_url,并携带
code(一次性 ticket)和 state。
请先校验 state,再由后端调用用户信息接口:
$state = $_GET['state'] ?? '';
if (empty($_SESSION['wx_state']) || !hash_equals($_SESSION['wx_state'], $state)) {
exit('state 校验失败,请重新登录');
}
unset($_SESSION['wx_state']);
$ch = curl_init('https://login.fps5.cn/api/user/info');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'client_id' => 'ak_你的client_id',
'client_secret' => 'sk_你的client_secret',
'code' => $_GET['code'] ?? '',
]),
]);
$json = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($json['code'] ?? 0) !== 200) {
exit('登录失败:' . ($json['msg'] ?? '未知错误'));
}
// 建议用 unionid 作为跨站点统一用户标识
$_SESSION['openid'] = $json['data']['openid'];
$_SESSION['unionid'] = $json['data']['unionid'];
$_SESSION['user'] = $json['data'];
header('Location: /index.php');
exit;
40003;若用户刷新回调页导致失败,请引导用户重新发起登录。
业务后端拿到用户资料后,请立即建立自己的会话。
6.3 用 unionid 识别同一个人
同一个微信开放平台账号下,不同网站应用返回的 openid 不同,但 unionid 一致。
如果多个业务站需要共享账号体系,请以 unionid 作为主键;
仅单站使用时,使用 openid 即可。
7. 网关用户体系
除了给业务站提供 OAuth 中转,网关自身也内置了微信扫码注册 / 登录: 用户访问 /user/login 扫码,首次自动创建账号,之后直接登录。
| 地址 | 作用 |
|---|---|
/user/login | 发起微信扫码登录;未注册用户自动完成注册。 |
/register | 与登录一致,首次扫码即注册。 |
/user | 用户中心:资料、openid / unionid、登录统计。 |
/user/logout | 退出当前登录态。 |
- 用户身份以
openid为唯一键,重复扫码只更新资料,不会重复建号。 - 用户数据保存在
gw_users表,可通过status字段停用账号。 - 用户登录与业务站授权互不影响:用户登录态保存在网关 Session,业务站仍通过 ticket 获取资料。
- 模拟模式下,用户登录会直接以模拟用户进入用户中心,便于本地验证页面。
8. 上线检查清单
- 网关域名已启用 HTTPS,证书有效。
- 微信开放平台「授权回调域」= 网关域名(不带协议与路径)。
- 后台已填写 AppID / AppSecret,且 模拟模式已关闭。
- 网关地址与真实访问地址完全一致(协议、域名均一致)。
- 每个业务项目都配置了明确的允许域名,未留空。
- client_secret 只存在于业务后端,未出现在前端或仓库中。
- 业务回调已校验 state,并在失败时给出重新登录入口。
database/已纳入备份,SQLite 文件不会随发布被覆盖。- 已用真实微信完整走通一次:扫码 → 回调 → 换取用户 → 建立会话。
9. 常见问题
微信提示「redirect_uri 参数错误」怎么办?
说明微信后台的授权回调域与网关实际域名不一致。请确认回调域只填域名
(如 login.fps5.cn),且网关地址使用同一域名与协议。
跳转网关后提示 redirect_url 不合法?
回调地址必须是 http(s) 绝对地址,且主机名在项目的「允许域名」内。
支持 *.example.com 通配;留空的项目表示不限制。
换票返回 40003 是什么原因?
ticket 无效、已过期或已被使用。ticket 默认 300 秒且仅能消费一次, 请让用户重新发起登录,不要重复提交同一个 code。
返回的 unionid 为空?
通常是网站应用未绑定微信开放平台账号,或授权时未满足 unionid 的返回条件。 请在开放平台确认应用归属,并重新扫码测试。
用户昵称为空或头像缺失?
网关在拉取用户资料失败时会降级使用 token 中的基础信息,保证登录可用。 可在后台授权日志中核对记录,必要时让用户重新扫码。
SQLite 能支撑多少用户?
SQLite 适合中小流量与内部系统,读多写少的登录场景足够。 如果并发写入很高,可将数据库切换为 MySQL,业务代码使用 ThinkPHP Db 门面,迁移成本较低。
如何本地联调而不调用微信?
在后台「系统设置」开启模拟模式,网关会跳过微信接口并生成模拟用户。 本地验证完成后请务必关闭,避免线上产生假用户。
参数、返回结构、错误码与多语言调用示例。