追梦学习库

QQ 登录无法跳转 QQ 互联 —— 排查与修复全过程记录

追梦君 88 阅读
文章摘要

站点: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 互联授权页。


七、后续事项

  1. 浏览器实测:无痕窗口打开 QQ 登录链接,确认正常弹出授权页。

  2. 核对回调地址:若授权回调报 redirect_uri 不匹配,去 QQ 互联后台 确认回调地址登记为 https://zhuimeng.vip/oauth/qq

  3. 备份处理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
  4. 扩展排查:若站内其他 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 状态码验证。

分享到:

发表评论

请先 登录 再评论