当前位置: 首页 > news >正文

PHP开放平台与OAuth认证服务

PHP开放平台与OAuth认证服务

OAuth是开放平台的标准授权协议。PHP可以实现完整的OAuth服务端和客户端。今天说说PHP中OAuth认证的实现。

OAuth的核心流程包括授权码模式、密码模式和客户端模式。授权码模式是最安全的。

```php
class OAuthServer
{
private PDO $pdo;

public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
$this->initSchema();
}

private function initSchema(): void
{
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS oauth_clients (
id INT AUTO_INCREMENT PRIMARY KEY,
client_id VARCHAR(80) NOT NULL UNIQUE,
client_secret VARCHAR(200) NOT NULL,
redirect_uri TEXT,
grant_types VARCHAR(200),
name VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
");

$this->pdo->exec("
CREATE TABLE IF NOT EXISTS oauth_access_tokens (
id INT AUTO_INCREMENT PRIMARY KEY,
access_token VARCHAR(200) NOT NULL UNIQUE,
client_id VARCHAR(80) NOT NULL,
user_id VARCHAR(80),
scope VARCHAR(200),
expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
");

$this->pdo->exec("
CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (
id INT AUTO_INCREMENT PRIMARY KEY,
refresh_token VARCHAR(200) NOT NULL UNIQUE,
client_id VARCHAR(80) NOT NULL,
user_id VARCHAR(80),
expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
");

$this->pdo->exec("
CREATE TABLE IF NOT EXISTS oauth_authorization_codes (
id INT AUTO_INCREMENT PRIMARY KEY,
authorization_code VARCHAR(200) NOT NULL UNIQUE,
client_id VARCHAR(80) NOT NULL,
user_id VARCHAR(80),
redirect_uri TEXT,
expires_at TIMESTAMP,
scope VARCHAR(200)
)
");
}

public function registerClient(string $name, string $redirectUri): array
{
$clientId = bin2hex(random_bytes(16));
$clientSecret = bin2hex(random_bytes(32));

$stmt = $this->pdo->prepare("
INSERT INTO oauth_clients (client_id, client_secret, redirect_uri, grant_types, name)
VALUES (?, ?, ?, ?, ?)
");
$stmt->execute([$clientId, $clientSecret, $redirectUri, 'authorization_code,refresh_token', $name]);

return ['client_id' => $clientId, 'client_secret' => $clientSecret];
}

public function createAuthorizationCode(string $clientId, string $userId, string $redirectUri): string
{
$code = bin2hex(random_bytes(20));

$stmt = $this->pdo->prepare("
INSERT INTO oauth_authorization_codes (authorization_code, client_id, user_id, redirect_uri, expires_at, scope)
VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 10 MINUTE), 'basic')
");
$stmt->execute([$code, $clientId, $userId, $redirectUri]);

return $code;
}

public function issueAccessToken(string $code, string $clientId, string $clientSecret): array
{
// 验证授权码
$stmt = $this->pdo->prepare("
SELECT * FROM oauth_authorization_codes
WHERE authorization_code = ? AND client_id = ? AND expires_at > NOW()
");
$stmt->execute([$code, $clientId]);
$authCode = $stmt->fetch();

if (!$authCode) {
throw new \RuntimeException("无效的授权码");
}

// 验证客户端
$client = $this->getClient($clientId);
if (!$client || !hash_equals($client['client_secret'], $clientSecret)) {
throw new \RuntimeException("客户端验证失败");
}

// 生成令牌
$accessToken = bin2hex(random_bytes(40));
$refreshToken = bin2hex(random_bytes(40));

$this->pdo->prepare("
INSERT INTO oauth_access_tokens (access_token, client_id, user_id, scope, expires_at)
VALUES (?, ?, ?, 'basic', DATE_ADD(NOW(), INTERVAL 1 HOUR))
")->execute([$accessToken, $clientId, $authCode['user_id']]);

$this->pdo->prepare("
INSERT INTO oauth_refresh_tokens (refresh_token, client_id, user_id, expires_at)
VALUES (?, ?, ?, DATE_ADD(NOW(), INTERVAL 30 DAY))
")->execute([$refreshToken, $clientId, $authCode['user_id']]);

// 删除已使用的授权码
$this->pdo->prepare("DELETE FROM oauth_authorization_codes WHERE authorization_code = ?")->execute([$code]);

return [
'access_token' => $accessToken,
'token_type' => 'Bearer',
'expires_in' => 3600,
'refresh_token' => $refreshToken,
'scope' => 'basic',
];
}

public function refreshAccessToken(string $refreshToken, string $clientId, string $clientSecret): array
{
$stmt = $this->pdo->prepare("
SELECT * FROM oauth_refresh_tokens
WHERE refresh_token = ? AND client_id = ? AND expires_at > NOW()
");
$stmt->execute([$refreshToken, $clientId]);
$token = $stmt->fetch();

if (!$token) {
throw new \RuntimeException("无效的刷新令牌");
}

// 验证客户端
$client = $this->getClient($clientId);
if (!$client || !hash_equals($client['client_secret'], $clientSecret)) {
throw new \RuntimeException("客户端验证失败");
}

$newAccessToken = bin2hex(random_bytes(40));
$this->pdo->prepare("
INSERT INTO oauth_access_tokens (access_token, client_id, user_id, scope, expires_at)
VALUES (?, ?, ?, 'basic', DATE_ADD(NOW(), INTERVAL 1 HOUR))
")->execute([$newAccessToken, $clientId, $token['user_id']]);

// 删除旧的刷新令牌
$this->pdo->prepare("DELETE FROM oauth_refresh_tokens WHERE refresh_token = ?")->execute([$refreshToken]);

return [
'access_token' => $newAccessToken,
'token_type' => 'Bearer',
'expires_in' => 3600,
'scope' => 'basic',
];
}

public function validateAccessToken(string $accessToken): ?array
{
$stmt = $this->pdo->prepare("
SELECT * FROM oauth_access_tokens
WHERE access_token = ? AND expires_at > NOW()
");
$stmt->execute([$accessToken]);
$token = $stmt->fetch(PDO::FETCH_ASSOC);
return $token ?: null;
}

public function getClient(string $clientId): ?array
{
$stmt = $this->pdo->prepare("SELECT * FROM oauth_clients WHERE client_id = ?");
$stmt->execute([$clientId]);
$client = $stmt->fetch(PDO::FETCH_ASSOC);
return $client ?: null;
}
}

$pdo = new PDO('mysql:host=localhost;dbname=oauth', 'root', '');
$oauth = new OAuthServer($pdo);

$client = $oauth->registerClient('我的应用', 'https://myapp.com/callback');
echo "客户端注册成功: " . json_encode($client) . "\n";

$code = $oauth->createAuthorizationCode($client['client_id'], 'user_123', 'https://myapp.com/callback');
echo "授权码: {$code}\n";

$token = $oauth->issueAccessToken($code, $client['client_id'], $client['secret']);
echo "访问令牌: " . json_encode($token, JSON_PRETTY_PRINT) . "\n";

$valid = $oauth->validateAccessToken($token['access_token']);
echo "令牌有效: " . ($valid ? '是' : '否') . "\n";
?>
>

OAuth中间件可以保护API端点:

```php
class OAuthMiddleware
{
private OAuthServer $server;

public function __construct(OAuthServer $server)
{
$this->server = $server;
}

public function authenticate(): ?array
{
$header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$token = str_replace('Bearer ', '', $header);

if (empty($token)) {
http_response_code(401);
echo json_encode(['error' => 'missing_token', 'message' => '缺少访问令牌']);
exit;
}

$tokenData = $this->server->validateAccessToken($token);
if ($tokenData === null) {
http_response_code(401);
echo json_encode(['error' => 'invalid_token', 'message' => '令牌无效或已过期']);
exit;
}

return $tokenData;
}
}
?>

OAuth是开放平台的认证基础设施。授权码模式是最安全的流程,适用于服务端应用。密码模式适用于信任的客户端,客户端模式适用于服务间通信。实现OAuth服务端要特别注意安全,包括CSRF防护、重定向URI验证和令牌的加密存储。

http://www.jsqmd.com/news/958763/

相关文章:

  • 语义压缩,才是提示词工程的底层心法
  • 5分钟上手BilibiliDown:免费B站视频下载器全攻略
  • 别再为官网下载发愁!CoppeliaSim/V-REP全版本安装包(Win/Mac/Linux)保姆级获取指南
  • 2026年近期济宁地区寻求高性价比食品输送带?这家制造商值得关注 - 2026年企业资讯
  • 实战应用:基于快马平台开发支持TokenP的多链资产看板管理工具
  • 标识牌设计公司推荐,哪家性价比高? - myqiye
  • 双面氧化应激:既是屏障,也是癌症转移推手
  • Hive SQL避坑指南:处理嵌套数据时,struct和named_struct到底该怎么选?
  • 3天掌握芋道源码企业级框架:从零搭建到实战开发的完整指南
  • 密码杂凑算法七大神剑之青干剑QGS设计原理详解
  • 手把手搭建 OpenClaw 智能助手,实现电脑自动化办公操作
  • 2026年GEO服务商选型必看!十大靠谱GEO源头工厂全维度评测推荐 + 科学避坑指南 - 玖叁鹿
  • 别再死记硬背Node2Vec公式了!用Python+PyTorch手搓一个随机游走节点嵌入(附完整代码)
  • PyAEDT:工程仿真智能化的革命性Python框架
  • 如何打造极致便携的Windows C/C++开发环境:w64devkit深度解析
  • HICO-Det数据集深度解析:从‘人骑自行车’到‘人喂斑马’,600种交互背后的标注逻辑与常见坑点
  • 2026年上海增量式直线位移传感器市场深度解析:如何选择优质供应商 - 2026年企业资讯
  • STM32CubeIDE实战:手把手教你配置CAN中断接收,告别轮询死等
  • Gemini会话留存率低于行业均值37%?5步动态权重调优法,72小时内拉升至81.4%(含Prometheus监控模板)
  • 单智能体(Single Agent)落地实践全指南:从 ReAct 到 Tool Use,构建真正可靠的 AI Agent
  • 免疫组织化学技术实验流程与操作规范详解
  • 海伯森3D线光谱共焦精密测量技术及产业化应用
  • 从手工到自动,不同行业的跨越难点有何异同?2026企业级AI Agent落地全指南
  • 别再手动调了!SAP SmartForms二维码排版终极指南:固定大小、对齐与打印优化
  • 用Python复现通达信Winner函数:手把手教你估算A股筹码分布与获利盘比例
  • 法律文书智能生成系统上线实录(从试点到全所推广仅47天)
  • 从‘过零点’到‘比特流’:手把手教你用Python仿真复现FSK软件解调全过程(含信号可视化)
  • PyTorch版DnCNN盲去噪完整工程:含训练脚本、测试流程、预训练权重与逐行中文注释
  • 【企业AI工具选型生死线】:从需求映射、数据兼容性到LLM微调支持度——一份被19家 Fortune 500 保密采用的评估矩阵
  • 手把手教你用STM32F103和ESP8266做一个桌面天气时钟(附完整代码和接线图)