OAuth 2.0 是現代第三方登錄的標準協議。本文以新浪微博開放平台為例,提供一套完整、生產就緒的 PHP 整合方案,涵蓋授權碼模式流程、Token 管理、用戶信息獲取、數據庫持久化、安全防護、錯誤處理與現代框架整合建議。
用戶點擊「微博登錄」
↓
重定向到微博授權頁 (authorize)
↓
用戶授權 → 微博重定向回 callback.php?code=AUTH_CODE
↓
後端用 code 換取 access_token + uid
↓
調用 users/show 獲取用戶詳細信息
↓
檢查本地數據庫:新用戶→註冊綁定 / 舊用戶→更新 Token
↓
建立本地會話 (Session/JWT) → 登錄成功跳轉首頁
CREATE TABLE `oauth_users` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`provider` VARCHAR(20) NOT NULL DEFAULT 'weibo', -- 可擴展 qq, wechat, github
`provider_uid` VARCHAR(64) NOT NULL, -- 微博 uid (字符串型大數字)
`access_token` VARCHAR(255) NOT NULL,
`refresh_token` VARCHAR(255) DEFAULT NULL, -- 微博不支持 refresh_token,保留擴展
`expires_in` INT UNSIGNED DEFAULT 0, -- access_token 過期時間戳
`username` VARCHAR(100) NOT NULL, -- 微博昵稱
`avatar` VARCHAR(500) DEFAULT NULL, -- 頭像 URL
`raw_data` JSON DEFAULT NULL, -- 完整用戶信息備份
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_provider_uid` (`provider`, `provider_uid`),
INDEX `idx_access_token` (`access_token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
config.php<?php
return [
'weibo' => [
'app_key' => 'YOUR_APP_KEY', // 客戶端 ID
'app_secret' => 'YOUR_APP_SECRET', // 客戶端密鑰
'redirect_uri' => 'https://yourdomain.com/callback.php', // 必須與開放平台配置一致
'authorize_url' => 'https://api.weibo.com/oauth2/authorize',
'token_url' => 'https://api.weibo.com/oauth2/access_token',
'api_base' => 'https://api.weibo.com/2/',
'scope' => 'all', // 權限範圍
],
'db' => [
'host' => 'localhost',
'name' => 'your_db',
'user' => 'db_user',
'pass' => 'db_pass',
'charset' => 'utf8mb4',
],
];
WeiboOAuth.php<?php
class WeiboOAuth {
private $config;
private $accessToken;
private $httpClient;
public function __construct(array $config, $accessToken = null) {
$this->config = $config;
$this->accessToken = $accessToken;
$this->httpClient = new \GuzzleHttp\Client([
'base_uri' => $config['api_base'],
'timeout' => 10.0,
'verify' => true, // 生產環境必須驗證 SSL
]);
}
// 1. 生成授權 URL
public function getAuthorizeUrl(array $params = []) {
$query = array_merge([
'client_id' => $this->config['app_key'],
'redirect_uri' => $this->config['redirect_uri'],
'response_type' => 'code',
'scope' => $this->config['scope'],
'state' => $this->generateState(), // CSRF 防護
], $params);
return $this->config['authorize_url'] . '?' . http_build_query($query);
}
// 2. 用授權碼換取 Access Token
public function getAccessToken($code) {
$response = $this->httpClient->post($this->config['token_url'], [
'form_params' => [
'client_id' => $this->config['app_key'],
'client_secret' => $this->config['app_secret'],
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $this->config['redirect_uri'],
],
]);
$data = json_decode($response->getBody()->getContents(), true);
if (isset($data['error'])) {
throw new \Exception("Weibo OAuth Error: {$data['error']} - {$data['error_description']}");
}
$this->accessToken = $data['access_token'];
return $data; // 包含 access_token, expires_in, uid, remind_in 等
}
// 3. 獲取用戶信息
public function getUserInfo($uid = null) {
if (!$this->accessToken) throw new \Exception('Access token not set');
$uid = $uid ?? $this->getUidFromToken(); // 或從 token 響應中獲取
$response = $this->httpClient->get('users/show.json', [
'query' => ['access_token' => $this->accessToken, 'uid' => $uid],
]);
$user = json_decode($response->getBody()->getContents(), true);
if (isset($user['error'])) throw new \Exception("API Error: {$user['error']}");
return $user;
}
// 4. 通用 API 調用
public function api($endpoint, $method = 'GET', $params = []) {
$params['access_token'] = $this->accessToken;
$response = $this->httpClient->request($method, $endpoint . '.json', [
$method === 'GET' ? 'query' : 'form_params' => $params,
]);
return json_decode($response->getBody()->getContents(), true);
}
private function generateState() {
$state = bin2hex(random_bytes(16));
$_SESSION['oauth_state'] = $state;
return $state;
}
public function verifyState($state) {
return isset($_SESSION['oauth_state']) && hash_equals($_SESSION['oauth_state'], $state);
}
}
login.php<?php
session_start();
require 'config.php';
require 'WeiboOAuth.php';
$config = require 'config.php';
$oauth = new WeiboOAuth($config['weibo']);
// 生成授權鏈接
$authUrl = $oauth->getAuthorizeUrl();
// 可選:將 state 存入 Session 防 CSRF
// $_SESSION['oauth_state'] = $oauth->getState(); // 已在 getAuthorizeUrl 內處理
header("Location: $authUrl");
exit;
callback.php(核心業務邏輯)<?php
session_start();
require 'config.php';
require 'WeiboOAuth.php';
$config = require 'config.php';
$oauth = new WeiboOAuth($config['weibo']);
// 1. 驗證 state (CSRF 防護)
if (empty($_GET['state']) || !$oauth->verifyState($_GET['state'])) {
die('Invalid state parameter. Possible CSRF attack.');
}
// 2. 處理授權失敗
if (isset($_GET['error'])) {
die("Authorization failed: {$_GET['error']} - {$_GET['error_description']}");
}
// 3. 用 code 換取 token
if (empty($_GET['code'])) die('Missing authorization code.');
try {
$tokenData = $oauth->getAccessToken($_GET['code']);
$accessToken = $tokenData['access_token'];
$uid = $tokenData['uid'];
$expiresIn = $tokenData['expires_in']; // 秒數
$expiresAt = time() + $expiresIn;
// 4. 獲取用戶詳細信息
$oauth->setAccessToken($accessToken); // 更新 SDK token
$userInfo = $oauth->getUserInfo($uid);
// 5. 數據庫操作:查找或創建用戶
$pdo = new PDO(
"mysql:host={$config['db']['host']};dbname={$config['db']['name']};charset={$config['db']['charset']}",
$config['db']['user'], $config['db']['pass'],
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$stmt = $pdo->prepare("SELECT * FROM oauth_users WHERE provider = 'weibo' AND provider_uid = ?");
$stmt->execute([$uid]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
// 更新 Token(微博 access_token 有效期 30 天,需定期刷新)
$stmt = $pdo->prepare("UPDATE oauth_users SET access_token = ?, expires_at = ?, raw_data = ?, updated_at = NOW() WHERE id = ?");
$stmt->execute([$accessToken, $expiresAt, json_encode($userInfo, JSON_UNESCAPED_UNICODE), $user['id']]);
} else {
// 新用戶註冊
$stmt = $pdo->prepare("INSERT INTO oauth_users (provider, provider_uid, access_token, expires_at, username, avatar, raw_data) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([
'weibo', $uid, $accessToken, $expiresAt,
$userInfo['screen_name'] ?? '微博用戶',
$userInfo['avatar_large'] ?? $userInfo['profile_image_url'] ?? '',
json_encode($userInfo, JSON_UNESCAPED_UNICODE)
]);
$user['id'] = $pdo->lastInsertId();
}
// 6. 建立本地會話
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['provider'] = 'weibo';
$_SESSION['login_time'] = time();
// 7. 重定向到首頁或原請求頁面
$redirect = $_SESSION['login_redirect'] ?? '/index.php';
unset($_SESSION['login_redirect']);
header("Location: $redirect");
exit;
} catch (\Exception $e) {
error_log("Weibo OAuth Error: " . $e->getMessage());
die('登錄失敗,請稍後重試。');
}
// composer require laravel/socialite
// config/services.php
'weibo' => [
'client_id' => env('WEIBO_KEY'),
'client_secret' => env('WEIBO_SECRET'),
'redirect' => env('WEIBO_REDIRECT_URI'),
],
// Controller
use Laravel\Socialite\Facades\Socialite;
public function redirect() { return Socialite::driver('weibo')->redirect(); }
public function callback() {
$weiboUser = Socialite::driver('weibo')->stateless()->user();
// $weiboUser->getId(), getNickname(), getAvatar(), token...
// 綁定/登錄邏輯...
}
配置 `knpu_oauth2_client` 客戶端,實現 `OAuth2ClientInterface`,享受依賴注入、事件分發、安全防護等開箱即用能力。
總結: 微博 OAuth 整合核心在於「標準授權碼流程」「Token 安全管理」「用戶數據冪等持久化」「CSRF/重放攻擊防護」。上文方案採用 Guzzle 替代原生 cURL,PDO 防注入,State 防 CSRF,結構清晰易維護。生產環境建議接入 Laravel Socialite 等成熟組件,並建立 Token 過期監控、用戶畫像同步、多賬號綁定解綁等完整用戶身份體系。