ThinkPHP不能内置OAuth2协议栈,必须手动实现授权码模式四步流程;自建授权服务器违背SSO原则且存在安全风险;跨域SSO需用JWT校验iss/aud/exp,禁用session存敏感信息;微信redirect_uri必须严格匹配后台配置。

ThinkPHP 本身不内置 OAuth2 协议栈,不能靠配置或一个中间件就“启用 OAuth2 登录”。它只作为 OAuth2 Client(客户端)角色存在,必须手动实现授权码模式四步流程:拼授权 URL → 处理回调 → 服务端换 token → 验证并登录用户。任何试图在 ThinkPHP 里自建 AuthorizationServer 的做法,既违背 SSO 架构原则,也直接踩中安全红线。
为什么不能用 think-auth 或 Session 实现跨系统 SSO
浏览器 Cookie 默认隔离域名,session_start() 产生的 PHPSESSID 在 a.example.com 和 b.example.com 之间完全无效。强行改 session.cookie_domain = ".example.com" 只对同根子域有效,且要求所有系统部署在同一主域下——这在真实多租户、SaaS 或微前端场景中几乎不可行。
更关键的是:think-auth 类库本质是本地会话管理,它不处理 access_token 签名验证、iss/aud/exp 校验、scope 动态比对等 OAuth2 必备逻辑。一旦授权中心返回 JWT,而你的 ThinkPHP 应用只是 base64_decode 后就信任 payload,等于主动绕过签名验证,攻击者可伪造任意用户身份。
- JWT 必须用
firebase/php-jwt的verify()方法校验签名,不能手写解码 -
iss(签发方)必须严格匹配授权中心域名,如"https://auth.example.com" -
aud(受众)必须包含当前 ThinkPHP 系统的 client_id,否则不是发给你的 -
exp是时间戳,需用time() > $payload['exp']判断,不是字符串比较
微信 OAuth2 授权时 redirect_uri mismatch 怎么 fix
这是开发期最高频的报错,根本原因不是 ThinkPHP 有问题,而是微信后台配置和代码生成的 URL 不一致。微信对 redirect_uri 校验极其严格:协议(http/https)、端口(8000/443)、路径(/callback/wechat)、末尾斜杠(有/无)、大小写,全部必须一字不差。
立即学习“PHP免费学习笔记(深入)”;
典型错误示例:
- 微信后台填了
https://a.example.com/callback/wechat/(带尾部斜杠),但 ThinkPHP 用url('wechat/callback')生成的是https://a.example.com/callback/wechat(无尾部斜杠)→ 报错 - 本地开发用
http://localhost:8000/callback/wechat,但没加到公众号“网页授权域名”白名单 → 报错 - 用了 Nginx 反向代理,ThinkPHP 拿到的
$_SERVER['HTTP_HOST']是内网地址(如192.168.1.100),拼出的 redirect_uri 自然不匹配 → 报错
解决办法只有两个:
- 开发阶段,在公众号后台“测试账号”或“网页授权域名”里,把完整、精确的 redirect_uri 加进去(包括 http/https、端口、路径、尾部斜杠)
- 生产环境,确保
url()生成的链接协议与部署环境一致;若走反代,需在 Nginx 中透传X-Forwarded-Proto并在 ThinkPHP 配置中开启url_html5和url_domain
用 league/oauth2-client 时 token 刷新和 scope 校验怎么落地
league/oauth2-client 只管协议流程,不自动持久化 token、不拦截 401、不校验 scope 是否被授予。漏掉任一环,上线后就会出现“用户登着登着就掉线”或“调接口返回空邮箱字段”。
- Token 存储必须服务端可控:别存
localStorage,推荐用数据库加密字段 +refresh_token,或短周期用$_SESSION(注意 session 生命周期要略长于 access_token) - 每次调 API 前检查
$token->getExpires(),它是整型时间戳,不是字符串;过期就得调$provider->getAccessToken('refresh_token', ['refresh_token' => $oldRefreshToken]) - Scope 必须显式比对:比如你代码里需要用户邮箱,就要检查
in_array('email', $token->getScopes());如果微信只返回['openid', 'unionid'],就得引导用户重新授权并传scope=email - 别用
file_get_contents请求/oauth/token:它默认不发Content-Type: application/x-www-form-urlencoded,且参数不自动 urlencode,极易 400;用guzzlehttp/guzzle更稳
access_token 换 userinfo 时 {"errcode":40003,"errmsg":"invalid openid"} 怎么查
这个错误不是网络问题,而是参数错配。微信的 /sns/userinfo 接口要求:access_token 和 openid 必须来自同一轮 /sns/oauth2/access_token 响应。常见错误是把 A 用户的 access_token 和 B 用户的 openid 混在一起发请求。
实操要点:
- 换 token 的接口是
https://api.weixin.qq.com/sns/oauth2/access_token,不是通用的/oauth/token;它返回的access_token是“网页授权专用 token”,仅限调/sns/userinfo和/sns/auth - 拿到响应后立刻
json_decode($res, true),从中取['access_token']和['openid'],两个值必须成对使用、一次有效 - code 5 分钟失效且只能用一次,ThinkPHP 收到
$_GET['code']后必须立刻发起换 token 请求,别先存 DB 再异步处理 - 拉 userinfo 时 URL 是
https://api.weixin.qq.com/sns/userinfo?access_token=xxx&openid=xxx&lang=zh_CN,注意lang参数不能少,否则可能返回乱码昵称
最易忽略的一点:微信的 access_token 有效期是 2 小时,但它不返回 expires_in 字段;你得自己记下请求时间,按 7200 秒算过期,不能依赖响应里的字段。



















