首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

新浪微博 OAuth 2.0 整合完整指南:從授權流程到用戶數據持久化

     編輯:PHP 相關知識

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',
    ],
];

核心 SDK 封裝 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('登錄失敗,請稍後重試。');
}

安全加固清單

  • State 參數防 CSRF: 每次授權生成隨機 `state`,回調驗證(上文已實現)
  • PKCE (Proof Key for Code Exchange): 移動端/SPA 必須,防授權碼劫持。微博尚不完全支持,關注文檔更新
  • HTTPS 強制: 生產環境所有回調、API 均需 HTTPS
  • Token 安全存儲: 數據庫加密存儲 `access_token`(如 `openssl_encrypt`),日誌脫敏
  • 最小權限原則: 僅申請必要 `scope`,避免 `all`
  • Token 刷新機制: 微博 access_token 30 天過期,需在 API 調用前檢查 `expires_at`,過期引導用戶重新授權(微博不提供 refresh_token)

現代框架整合建議

Laravel (Socialite)

// 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...
    // 綁定/登錄邏輯...
}

Symfony (HWIOAuthBundle / KnpUOAuth2ClientBundle)

配置 `knpu_oauth2_client` 客戶端,實現 `OAuth2ClientInterface`,享受依賴注入、事件分發、安全防護等開箱即用能力。


總結: 微博 OAuth 整合核心在於「標準授權碼流程」「Token 安全管理」「用戶數據冪等持久化」「CSRF/重放攻擊防護」。上文方案採用 Guzzle 替代原生 cURL,PDO 防注入,State 防 CSRF,結構清晰易維護。生產環境建議接入 Laravel Socialite 等成熟組件,並建立 Token 過期監控、用戶畫像同步、多賬號綁定解綁等完整用戶身份體系。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved