当前位置: 首页 > 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.gsyq.cn/news/1470439.html

相关文章:

  • 5分钟上手BilibiliDown:免费B站视频下载器全攻略
  • 异辛基三乙氧基硅烷技术解析与合规供应选型指南:环氧灌浆料/硅烷浸渍剂/硅烷膏体/自密实混凝士/铝酸盐无机防腐砂浆/选择指南 - 优质品牌商家
  • 谁能拒绝一枚月光做成的耳机✨
  • 2026年近期济宁地区寻求高性价比食品输送带?这家制造商值得关注 - 2026年企业资讯
  • 别再死记硬背Node2Vec公式了!用Python+PyTorch手搓一个随机游走节点嵌入(附完整代码)
  • 3天掌握芋道源码企业级框架:从零搭建到实战开发的完整指南
  • Gemini会话留存率低于行业均值37%?5步动态权重调优法,72小时内拉升至81.4%(含Prometheus监控模板)
  • 2026年上海增量式直线位移传感器市场深度解析:如何选择优质供应商 - 2026年企业资讯
  • 用Python复现通达信Winner函数:手把手教你估算A股筹码分布与获利盘比例
  • 免费解锁Wand专业版:终极完整指南与远程控制教程
  • 成都危险品物流仓储核心技术规范与合规实操指南:成都危险品物流仓储/成都危险品贮存/成都危险货物危险品仓库/危险化学品储存/选择指南 - 优质品牌商家
  • 从‘过零点’到‘比特流’:手把手教你用Python仿真复现FSK软件解调全过程(含信号可视化)
  • 实战演练,基于快马平台jdk17环境快速搭建restful api微服务
  • 非参数核聚类与老虎机反馈:理论与应用解析
  • 基于STM32与AD9851的双通道可编程波形发生器,支持基波+5次谐波叠加及三种基础波形输出
  • 2026年Q2嘉兴奢侈品回收实测:嘉兴名鉴钟表有限公司联系/嘉兴首饰回收/嘉兴奢侈品回收/嘉兴工艺美术品回收/嘉兴黄金回收/选择指南 - 优质品牌商家
  • 教资科三知识点汇总|初中高中各学科重点笔记整理
  • Windows + Trae 安装使用 CodeGraph 完整指南
  • 鸿蒙开发--CANNKit-AscendC-sobel
  • 保姆级教程:Halcon 18.11.0.1 Windows版从下载到激活全流程(含GigE驱动安装)
  • AI辅助开发:利用快马平台实现智能自适应的sweezy-cursors动画
  • SMT贴片加工锡膏储存和使用注意事项
  • 杰理之IO_CONTROL 功能介绍可以参考【篇】
  • 碳硅共生认知场方程:碳基-硅基协同智能的数学基础(世毫九实验室原创研究)
  • 【AI家庭中枢搭建指南】:20年智能家居架构师亲授7大避坑法则与实时联动配置秘籍
  • Mi-Create:如何为2021年后小米穿戴设备开发个性化表盘的完整技术指南
  • 别再乱用马尔可夫链了!先花5分钟用Excel自带的CHISQ.TEST做个马氏性预检验
  • 别再手动导ROM了!教你搭建一个免下载、即点即玩的Web版FC游戏库
  • OSPF联邦作业
  • Sunshine游戏串流实战指南:构建低延迟自托管云游戏平台的完整技术方案