QQ 登录无法跳转 QQ 互联 —— 排查与修复全过程记录
站点:zhuimeng.vip 程序:Typecho + Mirai 主题 + MiraiCore 插件 部署:Docker 容器(typecho2,镜像 typecho-php84-ssl,Apache) 记录日期:2026-08-17 文档版本:v1.0 结论摘要 QQ 登录无法跳转的根因是:后端返回 HTTP 200 而不是 302 重定向。浏览器只...
站点:
zhuimeng.vip
程序:Typecho + Mirai 主题 + MiraiCore 插件
部署:Docker 容器(typecho2,镜像typecho-php84-ssl,Apache)
记录日期:2026-08-17
文档版本:v1.0
结论摘要
QQ 登录无法跳转的根因是:后端返回 HTTP 200 而不是 302 重定向。浏览器只在收到 3xx 时才自动跟随 Location 跳转,200 状态的 Location 头会被直接忽略,页面停在空白处。
进一步定位到代码层:Typecho 的 Response 单例默认状态码为 200,并在脚本退出阶段统一发送响应头,覆盖了业务代码里 header('Location: ...') 的 302。
修复方式:在每次跳转前显式调用 \Typecho\Response::getInstance()->setStatus(302);。
修复状态:✅ 已修复,线上状态码 200 → 302。
一、问题现象
| 项目 | 内容 |
|---|---|
| 触发链接 | https://zhuimeng.vip/oauth/qq?action=login&rurl=https%3A%2F%2Fzhuimeng.vip%2F |
| 现象 | 点击 QQ 登录后,浏览器停留在 zhuimeng.vip 空白页,不跳转到 QQ 互联授权页 |
| 影响 | 第三方 QQ 登录完全不可用 |
二、排查过程(时间线)
步骤 1:HTTP 层诊断
用 curl 抓取 OAuth 链接的真实响应:
curl -sSI "https://zhuimeng.vip/oauth/qq?action=login&rurl=https%3A%2F%2Fzhuimeng.vip%2F" | head -1
输出:
HTTP/2 200 ← 应为 302
完整响应头里同时出现:
location: https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=1905398305&redirect_uri=...&state=...
x-powered-by: PHP/8.4.22
x-frame-options: SAMEORIGIN
content-type: text/html; charset=UTF-8
响应体为 0 字节(空页面)。
关键判断:Location 头已生成,但状态码是 200 而非 302。
步骤 2:排除参数与回调地址问题
Location 里的 client_id / redirect_uri / state 均正确,排除"参数错、回调地址错"的可能,锁定为状态码问题。
步骤 3:逐层定位代码(容器环境)
网站为 Docker 部署,代码在容器 typecho2 内的 /var/www/html。
| 排查位置 | 结论 |
|---|---|
主题目录 usr/themes/Mirai/ |
顶层无 oauth 文件 |
插件 MiraiCore(Auth.php / Plugin.php) |
Auth.php 仅做后台拦截;Plugin.php 只有 mirai-wechat,无 QQ OAuth |
全站搜 graph.qq.com |
命中 usr/themes/Mirai/vendor/yurunsoft/yurun-oauth-login/src/QQ/OAuth2.php(第三方库,只生成 URL,不负责跳转) |
搜 getAuthUrl 调用方 |
命中 usr/themes/Mirai/modules/oauth.php(真正的跳转处理) |
关键定位命令:
# 找网站容器
docker ps
# 全站定位 graph.qq.com
docker exec typecho2 sh -c 'cd /var/www/html; find . -name "*.php" | xargs grep -ln "graph.qq.com" 2>/dev/null'
# 定位 getAuthUrl 调用方(排除 vendor)
docker exec typecho2 sh -c 'cd /var/www/html/usr/themes/Mirai; find . -path ./vendor -prune -o -name "*.php" -print | xargs grep -ln "getAuthUrl" 2>/dev/null'
步骤 4:锁定根因
oauth.php 里的跳转写法本身正确:
header('Location: ' . $url);
exit;
但响应头里 200 与 Typecho 特有的安全头(x-frame-options 等)同时出现,说明是 Typecho 框架的 Response 机制覆盖了状态码。
三、根因分析
Typecho 用 Response 单例管理响应状态码(默认 200),并在脚本退出阶段通过 sendHeaders() 统一发送状态行和响应头。
业务代码里的 header('Location: ...') 虽然正确设置了 Location 头,但没有同步修改 Typecho Response 对象的状态码。最终 sendHeaders() 发送的 HTTP/1.1 200 覆盖了 PHP 隐式的 302,导致浏览器收到 200 + Location,不跳转。
结论:不是参数问题,不是跳转写法问题,而是 Typecho 框架与原生
header()的状态码冲突。
四、修复方案
在 oauth.php 的每一处 header('Location: ...') 之前,显式设置 Typecho Response 状态码为 302:
\Typecho\Response::getInstance()->setStatus(302);
header('Location: ' . $url);
exit;
共修复三处(同一文件 usr/themes/Mirai/modules/oauth.php):
| 位置 | 代码 |
|---|---|
| 彩虹聚合登录发起 | header('Location: ' . $result['url']); |
| QQ 等 OAuth 登录发起 | header('Location: ' . $url);(本次问题关键) |
| 回调后跳转 | header('Location: ' . $redirectUrl); |
五、修复执行记录
5.1 备份
docker exec typecho2 cp /var/www/html/usr/themes/Mirai/modules/oauth.php \
/var/www/html/usr/themes/Mirai/modules/oauth.php.bak
5.2 第一次尝试(sed,失败)
用 sed 正则替换时,命令里的 \(、\)、\' 等转义在终端粘贴时被破坏(\(' 变成 $'),导致替换未生效,文件未改动。
教训:多层 shell 转义(sed 正则 + 双引号 + docker exec)极易被终端破坏,改用更稳妥的方式。
5.3 第二次尝试(PHP 补丁,成功)
改用 PHP 脚本通过单引号 heredoc 传入容器执行,内容原样传输,避免 shell 转义:
docker exec -i typecho2 php << 'PHPEOF'
<?php
$f = '/var/www/html/usr/themes/Mirai/modules/oauth.php';
$c = file_get_contents($f);
$c = str_replace("header('Location: ' . \$result['url']);",
"\\Typecho\\Response::getInstance()->setStatus(302);\n header('Location: ' . \$result['url']);", $c);
$c = str_replace("header('Location: ' . \$url);",
"\\Typecho\\Response::getInstance()->setStatus(302);\n header('Location: ' . \$url);", $c);
$c = str_replace("header('Location: ' . \$redirectUrl);",
"\\Typecho\\Response::getInstance()->setStatus(302);\n header('Location: ' . \$redirectUrl);", $c);
file_put_contents($f, $c);
echo "patched\n";
PHPEOF
执行输出 patched,三处跳转均成功插入 setStatus(302)。
六、验证结果
6.1 代码确认
docker exec typecho2 grep -n "setStatus\|header('Location" /var/www/html/usr/themes/Mirai/modules/oauth.php
输出:
59: \Typecho\Response::getInstance()->setStatus(302);
60: header('Location: ' . $result['url']);
123: \Typecho\Response::getInstance()->setStatus(302);
124: header('Location: ' . $url);
191: \Typecho\Response::getInstance()->setStatus(302);
192: header('Location: ' . $redirectUrl);
6.2 线上状态码确认
curl -sSI "https://zhuimeng.vip/oauth/qq?action=login&rurl=https%3A%2F%2Fzhuimeng.vip%2F" | head -1
HTTP/2 200 ← 修复前
HTTP/2 302 ← 修复后 ✓
状态码已从 200 变为 302,浏览器将正常跳转 QQ 互联授权页。
七、后续事项
-
浏览器实测:无痕窗口打开 QQ 登录链接,确认正常弹出授权页。
-
核对回调地址:若授权回调报
redirect_uri不匹配,去 QQ 互联后台 确认回调地址登记为https://zhuimeng.vip/oauth/qq。 -
备份处理:
oauth.php.bak保留至登录流程稳定后再删;回滚命令:docker exec typecho2 cp /var/www/html/usr/themes/Mirai/modules/oauth.php.bak \ /var/www/html/usr/themes/Mirai/modules/oauth.php -
扩展排查:若站内其他 Typecho 第三方登录(微信等)或回调跳转存在同类"不跳转"问题,同法在对应跳转处加
setStatus(302)。
附录:涉及文件与关键命令
| 文件 / 命令 | 说明 |
|---|---|
usr/themes/Mirai/modules/oauth.php |
OAuth 跳转处理(已修复) |
usr/themes/Mirai/vendor/yurunsoft/yurun-oauth-login/src/QQ/OAuth2.php |
QQ OAuth2 SDK(生成授权 URL,未改动) |
usr/plugins/MiraiCore/Auth.php / Plugin.php |
排查中排除(无 QQ OAuth 逻辑) |
docker ps |
定位网站容器 typecho2 |
grep -rln "graph.qq.com" |
全站定位 OAuth 代码 |
curl -sSI |
验证 HTTP 状态码 |
本文档为问题排查修复的完整记录,用于复盘与归档。修复已通过线上 302 状态码验证。
发表评论