学完这篇,你能在纯 PHP 环境(不需要 Composer、不需要任何 SDK)里独立完成一套 OAuth 2.0 第三方登录,从拉起授权页到本地账号绑定全部跑通。
第一步:先在平台侧拿到三样东西
不管接微信、QQ、GitHub 还是 Gitee,流程都是同一套「授权码模式」。先去开放平台注册应用,你会拿到:
client_id(有的平台叫 AppID)
client_secret(AppSecret,绝不能出现在前端代码里)
redirect_uri(回调地址,必须提前在后台登记)
回调地址填成 https://你的域名/oauth/callback.php。
注意:redirect_uri 是逐字符比对的,少一个斜杠、http 写成 https、带不带末尾 /、带不带 www,都会直接报「redirect_uri 参数错误」。这个坑几乎人人踩一次。
第二步:拼授权地址,把用户送去平台
在登录页放一个「使用 XX 登录」按钮,跳转到下面这种地址:
$state = bin2hex(random_bytes(16));
$_SESSION['oauth_state'] = $state;
$params = [
'client_id' => $config['client_id'],
'redirect_uri' => $config['redirect_uri'],
'response_type' => 'code',
'scope' => 'user:email', // 按平台要求填
'state' => $state,
];
header('Location: https://github.com/login/oauth/authorize?' . http_build_query($params));
state 是防 CSRF 的关键:它是你生成的随机串,存在 session 里,平台回调时会原样带回。
注意:微信的授权地址末尾要拼 #wechat_redirect,否则在微信内置浏览器里会反复跳转。各家平台的授权路径不一样(GitHub 是 /login/oauth/authorize,QQ 是 graph.qq.com/oauth2.0/authorize),照着文档抄,别凭记忆。
第三步:回调页里用 code 换 access_token
用户同意授权后,平台会把你跳回 redirect_uri?code=xxx&state=xxx。回调页第一件事是校验 state:
if (empty($_GET['state']) || $_GET['state'] !== ($_SESSION['oauth_state'] ?? '')) {
exit('state 校验失败');
}
unset($_SESSION['oauth_state']);
校验通过再拿 code 换 token。这一步是服务器之间的 POST 请求,用 curl 就行:
function httpPost(string $url, array $data, array $headers = []): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => $headers,
]);
$body = curl_exec($ch);
curl_close($ch);
return json_decode($body, true) ?: [];
}
$token = httpPost('https://github.com/login/oauth/access_token', [
'client_id' => $config['client_id'],
'client_secret' => $config['client_secret'],
'code' => $_GET['code'],
'redirect_uri' => $config['redirect_uri'],
], ['Accept: application/json']);
$token['access_token'] 就是你要的东西。
注意:GitHub 不加 Accept: application/json 会返回 access_token=xxx&scope= 的字符串,json_decode 出来是 null,排查半天以为接口挂了。QQ 的 token 接口干脆返回 access_token=xxx&expires_in=7776000 的纯文本,得用 parse_str 解析。
注意:code 只能用一次,有效期一般 5~10 分钟。用户刷新回调页会报「code 已使用」,正确做法是换完 token 立刻重定向到站内页面,别停在回调 URL 上。
第四步:拿 token 拉用户信息
$ch = curl_init('https://api.github.com/user');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token['access_token'], 'User-Agent: your-site'],
]);
$user = json_decode(curl_exec($ch), true);
关键字段通常只有 openid(微信/QQ)或 id(GitHub),外加昵称和头像。唯一标识优先用 openid/id,不要用昵称——昵称可以改,也有重名。
注意:微信要拿昵称头像必须申请 snsapi_userinfo 权限,只申请 snsapi_base 的话只能拿到 openid。另外同一用户在公众号和网站下的 openid 不同,要打通得看 unionid。
第五步:绑定本地账号并建立登录态
拿到第三方唯一标识后,查你自己的用户表:
- 已绑定 → 直接写入登录 session
- 未绑定但邮箱匹配已有账号 → 让用户输入密码确认后绑定
- 完全新用户 → 建一条本地账号,把 openid 存进
oauth_bindings 表(字段:平台、openid、user_id、创建时间)
登录成功后立刻 session_regenerate_id(true) 防会话固定。判断"是否已登录"只看你自己的 session,不要拿 access_token 当凭证。
注意:别信第三方返回的邮箱就自动登录——有些平台邮箱未验证。至少要「邮箱 + 用户输密码」双确认,或者干脆要求新用户走一次注册流程。
第六步:安全兜底清单
client_secret 只放服务端配置,别进 Git,别写进 JS
- 所有出站请求开
CURLOPT_SSL_VERIFYPEER,别图省事关掉
- access_token 存服务端(要长期用时),过期就重新走授权,别硬撑
- 回调接口做限流,防止有人拿脚本刷 code
- 第三方接口超时要设,默认不设会卡死 PHP 进程
小结
OAuth 2.0 授权码模式的骨架就四步:**跳授权页拿 code → 服务端换 access_token → 拉 openid/用户信息 → 绑定本地账号发 session**。真正让新手卡住的不是流程,而是三个细节:redirect_uri 必须逐字符一致、code 一次性且短命、各平台返回格式不统一(JSON / 纯文本 / 带包装的 JSON)。把 state 校验和 token 只走服务端这两条守住,安全性基本就够用了。