PHP开放平台与OAuth认证服务
PHP开放平台与OAuth认证服务OAuth是开放平台的标准授权协议。PHP可以实现完整的OAuth服务端和客户端。今天说说PHP中OAuth认证的实现。OAuth的核心流程包括授权码模式、密码模式和客户端模式。授权码模式是最安全的。phpclass 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_codesWHERE 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_tokensWHERE 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_tokensWHERE 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:hostlocalhost;dbnameoauth, 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端点phpclass 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验证和令牌的加密存储。