1. 下载与运行
Demo 包含 7 个 PHP 文件与一份样式表,覆盖「申请二维码 → 扫码回调 → 换 ticket → 写 Session」 的完整链路,可直接作为业务站的登录模块基础。
curl、json、
session、mbstring 扩展;Nginx / Apache / 宝塔面板均可,无需 Composer。
# 1. 解压到站点目录(或子目录)
unzip wx-login-php-demo.zip -d /www/wwwroot/你的站点/
# 2. 编辑 config.php,填入「我的项目」中的凭证
# gateway / client_id / client_secret / redirect_url
# 3. 确认 redirect_url 的域名已在项目域名白名单内
# 4. 浏览器访问
https://你的站点/index.php
index.php 首页与主流程、qr.php 申请 / 刷新二维码、
callback.php 回调桥接、login.php 整页跳转、
logout.php 退出、lib.php 公共函数、config.php 配置。
2. 配置项
全部配置集中在 config.php,返回一个数组:
<?php
return [
'gateway' => 'https://login.fps5.cn',
'client_id' => 'ak_你的client_id',
'client_secret' => 'sk_你的client_secret',
'redirect_url' => 'https://你的站点/callback.php',
];
| 配置项 | 必填 | 说明 |
|---|---|---|
gateway |
是 | 登录网关地址,默认 https://login.fps5.cn。 |
client_id |
是 | 项目 client_id,在「我的项目」中查看。 |
client_secret |
是 | 项目 client_secret,只用于服务端换取用户资料,不会出现在前端。 |
redirect_url |
是 | 微信授权后回跳的地址,域名必须在项目白名单内,通常填 https://你的站点/callback.php。 |
callback.php 完成桥接,
因此路径请指向它,不要直接指到 index.php。
3. 登录链路
整个流程有两类 state,不要混用:业务 state 由本站生成并存入 Session;网关 state 由 /oauth/qr 返回,只用于网关与微信之间的校验。
GET https://login.fps5.cn/oauth/qr,服务端传 client_id、redirect_url 和本地生成的业务 state,拿到 appid / scope / redirect_uri / 网关 state。
页面加载微信官方 wxLogin.js,用 self_redirect: true 在登录卡片内渲染二维码。
用户在微信中确认授权后,二维码 iframe 自身跳转到 redirect_url,并带上 code 与网关 state。
callback.php 运行在第三方 iframe 上下文,不读写 Session,只把 code / state 交给顶层 index.php。
顶层页面与本站同源,Session 可用,取出并消费业务 state,一次性有效。
POST https://login.fps5.cn/api/user/info,用 client_id + client_secret + ticket 拿到 openid、unionid、昵称、头像,写入 Session。
4. 关键代码
完整实现见压缩包,核心只有三段。
4.1 服务端申请二维码参数
// 业务 state:回调时原样带回,用于校验来源
$bizState = bin2hex(random_bytes(16));
$query = http_build_query([
'client_id' => $config['client_id'],
'redirect_url' => $config['redirect_url'],
'state' => $bizState,
]);
[$http, $body] = demo_http_get(
rtrim($config['gateway'], '/') . '/oauth/qr?' . $query
);
$json = json_decode($body, true);
if ((int) ($json['code'] ?? 0) !== 200 || empty($json['data']['state'])) {
return ['ok' => false, 'msg' => $json['msg'] ?? '网关返回异常'];
}
// 只记录业务 state,不要记录网关内部 state
demo_remember_state($bizState);
return ['ok' => true, 'data' => $json['data']];
4.2 回调桥接页
$code = trim((string) ($_GET['code'] ?? ''));
$state = (string) ($_GET['state'] ?? '');
$query = [];
if ($code !== '') {
$query['wx_code'] = $code;
$query['wx_state'] = $state;
}
$target = 'index.php' . ($query ? '?' . http_build_query($query) : '');
?>
<script>
(function () {
var target = <?= json_encode($target) ?>;
try {
if (window.top && window.top !== window) {
// 顶层与本站同源,可安全导航
window.top.location.replace(target);
return;
}
} catch (e) {}
window.location.replace(target);
})();
</script>
4.3 顶层校验 state 并换取用户资料
$wxCode = trim((string) ($_GET['wx_code'] ?? ''));
$wxState = (string) ($_GET['wx_state'] ?? '');
if ($wxCode !== '') {
if (!demo_consume_state($wxState)) {
$loginError = '登录会话已过期或来源不可信,请重新扫码登录。';
} else {
[$http, $body] = demo_http_post(
rtrim($config['gateway'], '/') . '/api/user/info',
[
'client_id' => $config['client_id'],
'client_secret' => $config['client_secret'],
'code' => $wxCode,
]
);
$json = json_decode($body, true);
if ($http !== 200 || (int) ($json['code'] ?? 0) !== 200) {
$loginError = '换取用户信息失败:' . ($json['msg'] ?? '接口异常');
} else {
session_regenerate_id(true); // 防会话固定
$_SESSION['wx_user'] = $json['data'];
header('Location: index.php'); // 去掉地址栏里的 code
exit;
}
}
}
5. 常见问题
页面提示「请先完成配置」
config.php 里仍然是 ak_你的client_id 这类占位符。填入真实凭证并刷新页面。
提示「登录会话已过期或来源不可信」
通常是二维码放置超过 30 分钟,或刷新页面后又扫了旧二维码。点击「刷新二维码」重新发起即可。若频繁出现,检查服务器 session.save_path 是否可写。
提示「域名不在白名单」
redirect_url 的域名必须与项目里配置的域名一致,修改白名单后需要重新审核。
二维码区域空白或一直加载中
res.wx.qq.com 无法访问时会出现。可先用页面底部的「整页跳转登录」验证链路是否正常。
扫码后停在「正在返回…」
顶层跳转被浏览器安全策略拦截。页面已提供「点此继续」链接,点击即可;也可改用 login.php 的整页跳转方式。
服务器无法访问网关
确认服务器可出网访问 https://login.fps5.cn,并已开启 PHP 的 curl 或 allow_url_fopen。