OAuth 2.0 是業界標準的授權協議,廣泛用於第三方登錄、API 開放平台。本文記錄如何在 ThinkPHP(以下簡稱 TP)框架中集成 bshaffer/oauth2-server-php 庫,快速構建一個包含「授權碼模式」與「客戶端憑證模式」的 OAuth2 服務端。
執行以下 SQL 創建標準 OAuth2 數據表,包含:訪問令牌、授權碼、客戶端信息、公鑰、刷新令牌、作用域、用戶表。
-- oauth_access_tokens
CREATE TABLE `oauth_access_tokens` (
`access_token` VARCHAR(255) NOT NULL,
`client_id` VARCHAR(80) NOT NULL,
`user_id` VARCHAR(80) DEFAULT NULL,
`expires` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`scope` VARCHAR(2000) DEFAULT NULL,
PRIMARY KEY (`access_token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- oauth_authorization_codes
CREATE TABLE `oauth_authorization_codes` (
`authorization_code` VARCHAR(255) NOT NULL,
`client_id` VARCHAR(80) NOT NULL,
`user_id` VARCHAR(80) DEFAULT NULL,
`redirect_uri` VARCHAR(2000) DEFAULT NULL,
`expires` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`scope` VARCHAR(2000) DEFAULT NULL,
`id_token` VARCHAR(2000) DEFAULT NULL,
PRIMARY KEY (`authorization_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- oauth_clients
CREATE TABLE `oauth_clients` (
`client_id` VARCHAR(80) NOT NULL,
`client_secret` VARCHAR(80) NOT NULL,
`redirect_uri` VARCHAR(2000) NOT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `oauth_clients` (`client_id`, `client_secret`, `redirect_uri`) VALUES
('demoapp', 'demopass', 'https://127.0.0.1/tp/index.php');
-- oauth_refresh_tokens
CREATE TABLE `oauth_refresh_tokens` (
`refresh_token` VARCHAR(255) NOT NULL,
`client_id` VARCHAR(80) NOT NULL,
`user_id` VARCHAR(80) DEFAULT NULL,
`expires` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`scope` VARCHAR(2000) DEFAULT NULL,
PRIMARY KEY (`refresh_token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- oauth_scopes
CREATE TABLE `oauth_scopes` (
`scope` VARCHAR(255) NOT NULL,
`is_default` TINYINT(1) DEFAULT NULL,
PRIMARY KEY (`scope`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- oauth_users
CREATE TABLE `oauth_users` (
`username` VARCHAR(255) NOT NULL,
`password` VARCHAR(2000) DEFAULT NULL,
`first_name` VARCHAR(255) DEFAULT NULL,
`last_name` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Org/OAuth2/Controller.php封裝 PDO 存儲、Server 實例化、註冊 Grant Type(Authorization Code + Client Credentials)。
<?php
namespace Org\OAuth2;
class Controller
{
protected $oauth_server;
protected $oauth_storage;
protected $oauth_request;
protected $oauth_response;
public function __construct()
{
$this->oauth_storage = new \OAuth2\Storage\Pdo([
'dsn' => C('DSN'),
'username' => C('USERNAME'),
'password' => C('PASSWORD')
]);
$this->oauth_server = new \OAuth2\Server($this->oauth_storage);
$this->oauth_server->addGrantType(new \OAuth2\GrantType\ClientCredentials($this->oauth_storage));
$this->oauth_server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->oauth_storage));
$this->oauth_request = \OAuth2\Request::createFromGlobals();
$this->oauth_response = new \OAuth2\Response();
}
}
Org/OAuth2/Resource.php驗證 Access Token,自動拋出 401 錯誤。
<?php
namespace Org\OAuth2;
class Resource extends Controller
{
protected $tokenData;
public function __construct()
{
parent::__construct();
if (!$this->oauth_server->verifyResourceRequest(\OAuth2\Request::createFromGlobals())) {
$this->oauth_server->getResponse()->send();
die;
}
$this->tokenData = $this->oauth_server->getResourceController()->getToken();
}
}
Api/Controller/OAuth2Controller.php<?php
namespace Api\Controller;
class OAuth2Controller extends \Org\OAuth2\Controller
{
public function authorize()
{
if (!$this->oauth_server->validateAuthorizeRequest($this->oauth_request, $this->oauth_response)) {
$this->oauth_response->send();
die;
}
// 此處應展示用戶授權頁面,用戶確認後調用 handleAuthorizeRequest
$this->oauth_server->handleAuthorizeRequest($this->oauth_request, $this->oauth_response, true);
// 為演示直接返回 code
$code = substr($this->oauth_response->getHttpHeader('Location'), strpos($this->oauth_response->getHttpHeader('Location'), 'code=') + 5, 40);
echo json_encode(['code' => $code]);
}
public function token()
{
$this->oauth_server->handleTokenRequest(\OAuth2\Request::createFromGlobals())->send();
}
}
Api/Controller/TestController.php<?php
namespace Api\Controller;
class TestController extends \Org\OAuth2\Resource
{
public function test()
{
echo json_encode(['success' => true, 'message' => 'You accessed my APIs!']);
}
public function getToken()
{
echo json_encode(['token' => $this->tokenData]);
}
}
Conf/config.php<?php
require_once VENDOR_PATH . '/OAuth2/Autoloader.php';
\OAuth2\Autoloader::register();
return [
'DSN' => 'mysql:host=localhost;dbname=oauth2',
'USERNAME' => 'root',
'PASSWORD' => '',
'AUTOLOAD_NAMESPACE' => ['OAuth2' => VENDOR_PATH . 'OAuth2/'],
];
/api/oauth2/authorize?client_id=demoapp&redirect_uri=https://127.0.0.1/tp/index.php&response_type=code 獲取授權碼。/api/oauth2/token 獲取 access_token。Authorization: Bearer <access_token> 訪問 /api/test/test 驗證資源保護。redirect_uri 與客戶端註冊的一致,否則報 invalid_redirect_uri。client_secret。RefreshToken、JWT、Scope 細粒度權限控制。