PHP 怎么PHP授权码模式

wen PHP项目 2

本文目录导读:

PHP 怎么PHP授权码模式

  1. 基础概念
  2. 完整实现示例
  3. 使用示例
  4. 最佳实践建议

我来详细介绍 PHP 中实现授权码模式(Authorization Code)的方法,这是 OAuth 2.0 中最常见的授权方式。

基础概念

授权码模式流程:

用户 → 授权服务器(登录授权)→ 返回授权码 → 客户端 → 令牌服务器(换取Token)→ 返回访问令牌

完整实现示例

1 授权服务器端(Authorization Server)

<?php
// auth_server.php - 授权服务器
class AuthorizationServer {
    private $db;
    private $clientId = 'your_client_id';
    private $clientSecret = 'your_client_secret';
    private $redirectUri = 'http://localhost/callback.php';
    public function __construct() {
        // 连接数据库存储授权码和令牌
        $this->db = new PDO('mysql:host=localhost;dbname=oauth', 'root', 'password');
    }
    // 1. 处理授权请求
    public function authorize() {
        // 验证客户端参数
        $clientId = $_GET['client_id'] ?? '';
        $redirectUri = $_GET['redirect_uri'] ?? '';
        $responseType = $_GET['response_type'] ?? '';
        $state = $_GET['state'] ?? '';
        $scope = $_GET['scope'] ?? 'basic';
        // 验证客户端
        if ($clientId !== $this->clientId) {
            $this->sendError('invalid_client');
        }
        // 验证回调地址
        if ($redirectUri !== $this->redirectUri) {
            $this->sendError('invalid_redirect_uri');
        }
        // 检查用户是否已登录
        if (!$this->checkUserLoggedIn()) {
            // 跳转到登录页面
            $this->redirectToLogin($clientId, $redirectUri, $state, $scope);
        }
        // 生成授权码
        $authCode = $this->generateAuthorizationCode();
        $this->saveAuthorizationCode($authCode, $clientId, $_SESSION['user_id'], $scope);
        // 重定向回客户端应用
        $params = [
            'code' => $authCode,
            'state' => $state
        ];
        $redirectUrl = $redirectUri . '?' . http_build_query($params);
        header('Location: ' . $redirectUrl);
        exit;
    }
    // 2. 处理令牌请求
    public function token() {
        $code = $_POST['code'] ?? '';
        $clientId = $_POST['client_id'] ?? '';
        $clientSecret = $_POST['client_secret'] ?? '';
        $grantType = $_POST['grant_type'] ?? '';
        // 验证客户端身份
        if (!$this->validateClient($clientId, $clientSecret)) {
            $this->sendError('invalid_client');
        }
        // 验证授权码
        $authData = $this->getAuthorizationCode($code);
        if (!$authData || $authData['expires'] < time()) {
            $this->sendError('invalid_grant');
        }
        // 生成访问令牌和刷新令牌
        $accessToken = $this->generateAccessToken();
        $refreshToken = $this->generateRefreshToken();
        $expiresIn = 3600; // 1小时
        // 保存令牌
        $this->saveTokens([
            'access_token' => $accessToken,
            'refresh_token' => $refreshToken,
            'client_id' => $clientId,
            'user_id' => $authData['user_id'],
            'expires' => time() + $expiresIn
        ]);
        // 删除已使用的授权码
        $this->deleteAuthorizationCode($code);
        // 返回令牌
        header('Content-Type: application/json');
        echo json_encode([
            'access_token' => $accessToken,
            'token_type' => 'Bearer',
            'expires_in' => $expiresIn,
            'refresh_token' => $refreshToken,
            'scope' => $authData['scope']
        ]);
    }
    private function generateAuthorizationCode() {
        return bin2hex(random_bytes(32));
    }
    private function generateAccessToken() {
        return bin2hex(random_bytes(64));
    }
    private function generateRefreshToken() {
        return bin2hex(random_bytes(64));
    }
    private function saveAuthorizationCode($code, $clientId, $userId, $scope) {
        $stmt = $this->db->prepare(
            "INSERT INTO auth_codes (code, client_id, user_id, scope, expires) 
             VALUES (?, ?, ?, ?, ?)"
        );
        $stmt->execute([$code, $clientId, $userId, $scope, time() + 600]);
    }
    private function validateClient($clientId, $clientSecret) {
        return $clientId === $this->clientId && 
               $clientSecret === $this->clientSecret;
    }
    private function sendError($error) {
        header('Content-Type: application/json');
        echo json_encode(['error' => $error]);
        exit;
    }
}

2 客户端应用(Client Application)

<?php
// client.php - 客户端应用
class OAuthClient {
    private $clientId = 'your_client_id';
    private $clientSecret = 'your_client_secret';
    private $authorizationUrl = 'http://localhost/auth_server.php/authorize';
    private $tokenUrl = 'http://localhost/auth_server.php/token';
    private $redirectUri = 'http://localhost/callback.php';
    private $scope = 'read write';
    // 1. 发起授权请求
    public function startAuthorization() {
        $params = [
            'client_id' => $this->clientId,
            'redirect_uri' => $this->redirectUri,
            'response_type' => 'code',
            'scope' => $this->scope,
            'state' => bin2hex(random_bytes(16))
        ];
        // 保存state到session用于后续验证
        session_start();
        $_SESSION['oauth_state'] = $params['state'];
        $authUrl = $this->authorizationUrl . '?' . http_build_query($params);
        header('Location: ' . $authUrl);
        exit;
    }
    // 2. 处理回调
    public function handleCallback() {
        session_start();
        if (!isset($_GET['code'])) {
            $this->handleError('Authorization failed');
        }
        // 验证state参数
        if ($_GET['state'] !== $_SESSION['oauth_state']) {
            $this->handleError('Invalid state parameter');
        }
        $authorizationCode = $_GET['code'];
        // 3. 用授权码换取令牌
        return $this->exchangeCodeForToken($authorizationCode);
    }
    // 3. 用授权码换取访问令牌
    private function exchangeCodeForToken($code) {
        $postData = [
            'grant_type' => 'authorization_code',
            'code' => $code,
            'redirect_uri' => $this->redirectUri,
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret
        ];
        $ch = curl_init($this->tokenUrl);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/x-www-form-urlencoded'
        ]);
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($httpCode === 200) {
            $tokenData = json_decode($response, true);
            // 保存令牌到session或数据库
            session_start();
            $_SESSION['access_token'] = $tokenData['access_token'];
            $_SESSION['refresh_token'] = $tokenData['refresh_token'];
            $_SESSION['token_expires'] = time() + $tokenData['expires_in'];
            return $tokenData;
        } else {
            $this->handleError('Token exchange failed');
        }
    }
    // 4. 获取用户信息(使用访问令牌)
    public function fetchUserProfile() {
        session_start();
        if (empty($_SESSION['access_token'])) {
            $this->handleError('No access token available');
        }
        $ch = curl_init('http://localhost/api/user/profile');
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer ' . $_SESSION['access_token'],
            'Content-Type: application/json'
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($httpCode === 401) {
            // 令牌过期,尝试刷新
            $this->refreshAndRetry();
        }
        return json_decode($response, true);
    }
    // 5. 刷新令牌
    public function refreshToken() {
        session_start();
        $postData = [
            'grant_type' => 'refresh_token',
            'refresh_token' => $_SESSION['refresh_token'],
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret
        ];
        $ch = curl_init($this->tokenUrl);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        curl_close($ch);
        $tokenData = json_decode($response, true);
        if (isset($tokenData['access_token'])) {
            $_SESSION['access_token'] = $tokenData['access_token'];
            $_SESSION['refresh_token'] = $tokenData['refresh_token'];
            $_SESSION['token_expires'] = time() + $tokenData['expires_in'];
        }
        return $tokenData;
    }
    private function handleError($message) {
        header('Content-Type: application/json');
        echo json_encode(['error' => $message]);
        exit;
    }
}

3 回调处理页面

<?php
// callback.php - 回调处理
require_once 'client.php';
$oauthClient = new OAuthClient();
$result = $oauthClient->handleCallback();
// 成功获取令牌
header('Content-Type: application/json');
echo json_encode([
    'success' => true,
    'message' => 'Authorization successful',
    'token_type' => $result['token_type'],
    'expires_in' => $result['expires_in']
]);
?>

4 数据库表结构

-- 存储授权码
CREATE TABLE auth_codes (
    id INT AUTO_INCREMENT PRIMARY KEY,
    code VARCHAR(64) NOT NULL UNIQUE,
    client_id VARCHAR(50) NOT NULL,
    user_id INT NOT NULL,
    scope VARCHAR(255) DEFAULT 'basic',
    expires INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 存储访问令牌
CREATE TABLE access_tokens (
    id INT AUTO_INCREMENT PRIMARY KEY,
    access_token VARCHAR(128) NOT NULL UNIQUE,
    refresh_token VARCHAR(128) NOT NULL UNIQUE,
    client_id VARCHAR(50) NOT NULL,
    user_id INT NOT NULL,
    expires INT NOT NULL,
    scope VARCHAR(255) DEFAULT 'basic',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 存储客户端应用
CREATE TABLE oauth_clients (
    client_id VARCHAR(50) PRIMARY KEY,
    client_secret VARCHAR(100) NOT NULL,
    redirect_uri VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

使用示例

<?php
// 使用流程示例
// 页面1: 开始授权
if (isset($_GET['login'])) {
    $client = new OAuthClient();
    $client->startAuthorization();
}
// 页面2: 回调处理
if (isset($_GET['callback'])) {
    $client = new OAuthClient();
    $result = $client->handleCallback();
    // 获取用户信息
    $profile = $client->fetchUserProfile();
    echo json_encode($profile);
}
?>

最佳实践建议

  1. 安全性

    • 使用 HTTPS 传输
    • 验证所有输入参数
    • 设置令牌过期时间
    • 使用强随机数生成器
  2. 错误处理

    try {
        $client = new OAuthClient();
        $result = $client->handleCallback();
    } catch (Exception $e) {
        error_log($e->getMessage());
        // 记录错误
    }
  3. 令牌管理

    • 定期清理过期令牌
    • 支持令牌撤销
    • 令牌加密存储

这就是 PHP 授权码模式的完整实现,关键点是保证整个流程的安全性,并正确处理各种错误情况。

抱歉,评论功能暂时关闭!