简介:这是一份面向微信公众号开发者的源码资源包,围绕公众号登录场景提供最新的无限回调登录接口与多域名回调系统,能够解决微信公众平台对单个回调域名限制的常见问题,适合需要搭建多站点授权、对接第三方登录或统一账号体系的开发人员。资源压缩包共501个文件,整体大小8.5MB,以257个js脚本、86个php服务端文件、57个css样式表为主,同时包含png/jpg图标、sql数据库表及配置类文件,前端交互、后端接口与部署配置均有覆盖。目前已有370人学习下载。通过本资源可掌握公众号无限回调的实现思路、多域名配置方法以及回调验签流程,项目目录结构清晰,主要模块和功能注释较完整,方便直接部署到本地或服务器进行二次开发;其中回调机制还可迁移到现有业务系统,用于快速扩展第三方登录能力,对有公众号开发需求的个人开发者、外包团队或企业运维人员都具有实用价值。
1. 公众号多域名回调登录接口:把授权回调收口到一个网关
做公众号矩阵的团队几乎都会撞上同一个限制:微信公众平台后台的网页授权回调域名只能配置一个,但业务可能有a.example.com、b.example.com、m.example.com好几个。所谓“最新公众号无限回调登录接口源码”,真正的做法不是破解微信的限制,而是用一个独立网关统一接收微信回调,再根据参数把用户跳转回原来的业务域名。这套设计的核心是redirect_uri固定,state携带真实回跳地址,后端完成 code 换 openid。这篇文章会讲清网页授权原理,再给出一份基于 PHP + Redis 的可运行回调网关,覆盖多公众号分流、安全校验和验证方法,适合正在做公众号登录、矩阵账号体系的工程师。
2. 微信网页授权回调登录的原理与单域名困境
2.1 回调登录的本质是一次 code 交换
微信网页授权不是一个常驻会话,而是一组短时跳转。用户点击登录后,前端先访问微信的授权页https://open.weixin.qq.com/connect/oauth2/authorize,带上appid、redirect_uri、response_type=code、scope和state。微信确认用户身份后,302 回跳到你的redirect_uri,在 URL 上追加code和state。后端拿到 code 后,再用它去微信sns/oauth2/access_token接口换取access_token、openid、unionid。整个过程里,微信只会把 code 发送到你后台配置的那个域名下,任何协议、主机、端口不匹配都会得到“redirect_uri参数错误”。
一个标准的换取 token 请求,可以直接用 curl 模拟:
curl -G 'https://api.weixin.qq.com/sns/oauth2/access_token' \ --data-urlencode 'appid=wx123456789' \ --data-urlencode 'secret=your_app_secret' \ --data-urlencode 'code=CODE_FROM_CALLBACK' \ --data-urlencode 'grant_type=authorization_code'返回的 JSON 里最重要的字段是openid和access_token;如果errcode不为 0,常见原因包括 code 被重复使用、code 过期(5 分钟)、appid 与 secret 不匹配。写登录接口时,这一步应该包成一个独立的回调函数,方便在不同的公众号账号间复用。
2.2 业务域名与网页授权域名的区别
很多新手把“业务域名”和“网页授权域名”混在一起。微信公众平台的“业务域名”用于 JS 接口安全校验,比如调用wx.config里的开放标签,通常可以配置多个。而“网页授权域名”专门管 OAuth2 回跳,同一公众号同一时间只能有一个生效域名。这意味着你无法让 A 系统和 B 系统各自直接接收回调,只能让一个公共域名接收后,再跳到各自系统。
这里列一个表帮助区分:
| 配置项 | 作用 | 是否支持多个 |
|---|---|---|
| 服务器域名 | 接收微信消息推送或事件推送 | 可填多个 |
| 业务域名 | 前端 JS 接口调用时的安全域名 | 可填多个 |
| 网页授权域名 | OAuth2 登录回调地址 | 同一公众号只支持一个 |
| JS 安全域名 | 调用微信 JS-SDK 的域名 | 可填多个 |
所以“多域名回调”在微信官方限制下,唯一安全的落地方式就是做一个回调网关:所有业务端把用户引导到网关,网关统一接微信的 code,再通过 HTTP 302 把用户送回业务端自己提供的地址。这样微信后台永远只配置网关域名,业务域名可以随时增减,不需要反复改动公众号配置。
2.3 单域名限制下最容易翻车的三个点
第一,redirect_uri必须做 URL 编码,但很多代码直接拼字符串忘了urlencode,导致微信回调时无法识别。第二,state参数如果不校验,会收到外部构造的伪造回调,造成账号绑定混乱。第三,网关跳回业务端时如果直接暴露 openid,任何拿到这个地址的人都能伪装成该用户。正确做法是一次性 ticket 或临时 code,在业务端再换一次身份信息。理解了这三个点,下面实现起来就不会漏。
3. 用统一回调网关实现公众号多域名登录接口
3.1 网关的跳转链路设计
整个回调网关对外只提供两个地址:/login和/callback.php。业务端构造跳转链接时,把真实的业务回调地址放在redirect_uri参数里传给网关;网关用合法域名拼接微信授权链接,并把自己的/callback.php作为微信回调地址。用户授权完成后,微信把 code 发给网关;网关用自己的 appid 换取 openid,生成一次性ticket,最后 302 到业务端的真实地址,地址后带上ticket。业务端再拿着 ticket 请求网关的/verify接口,换取用户身份。
这个设计的关键是:redirect_uri不是固定的,而state也不是简单透传。网关需要用 Redis 保存一份临时映射,把state关联到本次登录的 appid 和业务回跳地址。这样既防止了 state 被篡改,也让同一个网关可以服务多个公众号。
3.2 PHP 实现最小可运行网关
下面是一份可以直接放进 PHP 项目里的最小实现,使用 Redis 做状态存储。先把配置写在config.php:
<?php return [ 'redis' => ['host' => '127.0.0.1', 'port' => 6379, 'db' => 2], 'apps' => [ 'wx123456789' => [ 'secret' => 'your_secret', 'name' => '矩阵A公众号' ], // 多个公众号继续往下加 ], ];然后是网关入口文件,分别处理action=login和action=callback:
<?php $config = require 'config.php'; $redis = new Redis(); $redis->connect($config['redis']['host'], $config['redis']['port']); $redis->select($config['redis']['db']); $action = $_GET['action'] ?? 'login'; if ($action === 'login') { $appid = $_GET['appid'] ?? ''; $redirect = $_GET['redirect_uri'] ?? ''; $apps = $config['apps']; if (!isset($apps[$appid])) { http_response_code(400); exit('appid 未注册'); } // 只允许业务白名单里的主机 $allowedHosts = ['a.example.com', 'b.example.com']; $host = parse_url($redirect, PHP_URL_HOST); if (!in_array($host, $allowedHosts, true)) { http_response_code(400); exit('redirect_uri 不在白名单'); } $state = bin2hex(random_bytes(16)); $redis->setex("oauth:state:$state", 300, json_encode([ 'appid' => $appid, 'redirect' => $redirect ])); $callback = 'https://oauth.example.com/gateway.php?action=callback'; $authUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize?' . 'appid=' . urlencode($appid) . '&redirect_uri=' . urlencode($callback) . '&response_type=code' . '&scope=snsapi_base' . '&state=' . $state . '#wechat_redirect'; header('Location: ' . $authUrl); exit; } if ($action === 'callback') { $code = $_GET['code'] ?? ''; $state = $_GET['state'] ?? ''; $raw = $redis->get("oauth:state:$state"); if (!$raw) { http_response_code(400); exit('state 过期或非法'); } $payload = json_decode($raw, true); $appid = $payload['appid']; $app = $config['apps'][$appid] ?? null; if (!$app) { http_response_code(400); exit('公众号配置不存在'); } // 用 code 换 token $api = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . 'appid=' . urlencode($appid) . '&secret=' . urlencode($app['secret']) . '&code=' . urlencode($code) . '&grant_type=authorization_code'; $response = file_get_contents($api); $token = json_decode($response, true); if (!isset($token['openid'])) { error_log('wechat token error: ' . $response); http_response_code(502); exit('换取 openid 失败'); } // 生成一次性票据,60 秒有效 $ticket = bin2hex(random_bytes(16)); $redis->setex("oauth:ticket:$ticket", 60, json_encode([ 'openid' => $token['openid'], 'unionid' => $token['unionid'] ?? '', 'appid' => $appid ])); $sep = strpos($payload['redirect'], '?') === false ? '?' : '&'; header('Location: ' . $payload['redirect'] . $sep . 'ticket=' . $ticket); exit; }这段代码的逻辑并不复杂:login分支负责校验参数并生成 state,callback分支负责接收微信回调、换 token、生成 ticket 并跳转回业务系统。注意callback里没有直接暴露 openid,而是生成一个 60 秒有效的票据,业务端拿票据后再换身份。这样即使 ticket 被捕获,有效期也很短,并且只能换一次性数据。
3.3 业务端解析 ticket 并完成登录
业务系统收到回调后,需要请求网关提供的/verify接口换取用户信息。网关可以再增加一个action=verify分支:
if ($action === 'verify') { $ticket = $_GET['ticket'] ?? ''; $raw = $redis->get("oauth:ticket:$ticket"); if (!$raw) { http_response_code(404); exit(json_encode(['error' => 'ticket 无效'])); } // 使用后立即删除,保证一次性 $redis->del("oauth:ticket:$ticket"); header('Content-Type: application/json'); echo $raw; exit; }业务端在回跳地址里取出ticket,请求这个接口就能获得 openid 和 unionid,然后执行自己的登录逻辑,比如setcookie或签发 JWT。这里的 ticket 相当于一个短期授权码,不能复用,因此比直接传 openid 安全得多。
4. 公众号矩阵的多账号回调分流与配置步骤
4.1 用 appid 区分不同公众号
多域名回调系统经常同时服务多个公众号,比如一个矩阵下有好几个垂直账号,每个账号的 AppID 和 AppSecret 都不同。网关只需要把每个公众号的凭证都写进config.php的apps数组里,业务端跳转时明确指定appid,网关就能在换取 token 时选择对应的 secret。微信的回调域名对所有公众号配置成同一个网关地址,由于微信回调时不校验公众号和域名的唯一归属,多个公众号可以使用同一个网页授权域名,只要每个公众号后台都填同一个域名即可。
实际运营中,往往是这样分工:
| 角色 | 示例 | 职责 |
|---|---|---|
| 业务端 A | a.example.com | 自有 H5,负责跳转和接收 ticket |
| 业务端 B | b.example.com | 另一个系统的 H5 |
| 回调网关 | oauth.example.com | 统一接收微信回调,交换票据 |
| 公众号 1 | wx111... | 对应 A 系统的用户体系 |
| 公众号 2 | wx222... | 对应 B 系统的用户体系 |
当用户从 B 系统发起登录时,B 构造的链接是https://oauth.example.com/gateway.php?action=login&appid=wx222...&redirect_uri=https%3A%2F%2Fb.example.com%2Flogin%2Fcallback。网关会带着这个 appid 去微信授权,等微信回跳后,网关才知道该用wx222...的 secret 换 token,最后跳回b.example.com。这就是“一个回调域名,服务多个公众号”的完整分流逻辑。
4.2 微信公众号后台需要做哪些配置
配置项不多,但每项都不能出错:
- 登录公众号后台,进入“设置与开发 - 公众号设置 - 功能设置”。
- 在“网页授权域名”里填网关的域名,例如
oauth.example.com。不要带https://和路径,也不建议用 IP。 - 在“基本配置”中复制 AppID 和 AppSecret,AppSecret 只会完整显示一次,丢失后需要重置。
- 在“IP 白名单”中加上网关服务器出口 IP,否则调用 access_token 接口时会报
40164错误。
注意,这里不涉及“服务器配置”里的 URL 和 Token,那是接收微信事件推送用的,与网页授权无关。很多人混淆后,反复检查服务器配置却忽略了网页授权域名,浪费大量时间。
4.3 加固 redirect_uri 白名单和防盗用
网关如果允许任意redirect_uri,攻击者可以构造一条链接,让用户跳到网关,再由网关带着用户的微信身份跳转到攻击者自己的域名,导致 openid 泄露。前面代码里已经用$allowedHosts做了域名白名单校验。实际项目中,白名单应该从配置表读取而不是写死在代码里,推荐放在数据库中,方便运营动态增加业务域名。
以下是常用的校验函数,可以复用到不同的入口:
function checkRedirectAllowed($redirectUrl, array $allowedHosts): bool { $host = parse_url($redirectUrl, PHP_URL_HOST); if (!$host) { return false; } if (in_array($host, $allowedHosts, true)) { return true; } // 支持子域名通配:.example.com foreach ($allowedHosts as $allowed) { if (strpos($allowed, '.') === 0 && preg_match('/' . preg_quote($allowed, '/') . '$/', $host)) { return true; } } return false; }这段函数优先精确匹配,其次支持带前导点的通配域名。配置时写.example.com就能允许a.example.com和shop.example.com。参数$redirectUrl是业务端传入的完整 URL,$allowedHosts是白名单数组。
4.4 Redis 状态表和票据表的设计
网关运行一段时间后,需要排查问题时,Redis 里的 key 很关键。建议用统一前缀区分:
| Key 格式 | 过期时间 | 内容 |
|---|---|---|
oauth:state:{state} | 300 秒 | 微信授权前的状态映射,存 appid 和回跳地址 |
oauth:ticket:{ticket} | 60 秒 | 授权后生成的临时票据,存 openid、unionid、appid |
代码里已经用了setex设置过期时间,不需要手动清理。每次verify成功后会删除 ticket,保证一次性。State 可以在用户完成回调后保留到过期,因为回跳地址已经在 Redis 里,不需要在前端传递敏感信息。
5. 用 curl 命令行验证公众号回调登录链路
5.1 模拟非法 state 和 code 验证回调入口
公众号登录链路里最容易出错的位置是网关回调,但微信的 code 需要真实用户授权才会签发,开发环境里难以自动获取。一个高效的方法是直接给网关的callback分支发送一个假的code和一个不存在的state,观察网关是否按预期返回错误页而不是 500。
用 curl 带上-v可以看到完整响应头和状态码:
curl -v 'https://oauth.example.com/gateway.php?action=callback&code=test123&state=not_exist' \ -H 'User-Agent: Mozilla/5.0'预期结果是 HTTP 400,响应体为state 过期或非法。如果返回 500,说明 Redis 连接或 PHP 语法有问题;如果返回 302,说明 state 校验逻辑被跳过了,这是严重的安全漏洞。之所以带上User-Agent,是因为部分服务器 WAF 会拦截无 UA 的请求,导致你误判网关自身故障。
5.2 验证 verify 接口的一次性逻辑
接着验证票据接口。先手动向 Redis 写入一个测试票据,或者在本地调试代码里生成一个,然后调用 verify 两次:
curl -s 'https://oauth.example.com/gateway.php?action=verify&ticket=YOUR_TEST_TICKET' | jq .第一次应该返回包含 openid 的 JSON,第二次应该返回ticket 无效。这个技巧能快速确认 Redis 的过期时间和删除逻辑是否生效。如果第一次和第二次返回一样,说明代码里漏了del操作,需要及时补上。
5.3 用 cURL 封装替代 file_get_contents
前面示例用了file_get_contents读取微信接口,但这种方式在网络抖动时没有任何超时控制,可能导致 PHP 进程长时间挂起。上线前建议替换成 cURL 封装,并设置连接和读取超时:
function getWechatToken($url, $timeout = 3): array { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); $body = curl_exec($ch); $errno = curl_errno($ch); curl_close($ch); if ($errno !== 0) { error_log("curl error: " . curl_strerror($errno)); return []; } return json_decode($body, true) ?? []; }这个函数里CURLOPT_CONNECTTIMEOUT控制建立 TCP 连接的最长等待时间,CURLOPT_TIMEOUT控制整个请求的最长耗时。如果微信接口响应慢或网络不可达,网关会快速返回 502,而不是卡住整个 PHP-FPM 进程。最后再用curl_error记录错误类型,方便对比是 DNS 问题还是连接超时。
本文还有配套的精品资源,点击获取