本文目录导读:

我来详细介绍 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);
}
?>
最佳实践建议
-
安全性
- 使用 HTTPS 传输
- 验证所有输入参数
- 设置令牌过期时间
- 使用强随机数生成器
-
错误处理
try { $client = new OAuthClient(); $result = $client->handleCallback(); } catch (Exception $e) { error_log($e->getMessage()); // 记录错误 } -
令牌管理
- 定期清理过期令牌
- 支持令牌撤销
- 令牌加密存储
这就是 PHP 授权码模式的完整实现,关键点是保证整个流程的安全性,并正确处理各种错误情况。