授權碼授權(Authorization Code Grant)
阿新 • • 發佈:2018-12-10
OAuth 2.0
不太熟悉什麼是OAuth2.0的同學可以參考阮大神的文章, 理解OAuth 2.0 - 阮一峰
授權碼模式(Authorization Code)
# 授權程式碼授予型別用於獲得訪問許可權令牌和重新整理令牌,併為機密客戶進行了優化。 # 由於這是一個基於重定向的流程,客戶端必須能夠與資源所有者的使用者代理(通常是Web)互動瀏覽器),能夠接收傳入請求(通過重定向)從授權伺服器。 # 授權程式碼流如下: +----------+ | Resource | | Owner | | | +----------+ ^ | (B) +----|-----+ Client Identifier +---------------+ | -+----(A)-- & Redirection URI ---->| | | User- | | Authorization | | Agent -+----(B)-- User authenticates --->| Server | | | | | | -+----(C)-- Authorization Code ---<| | +-|----|---+ +---------------+ | | ^ v (A) (C) | | | | | | ^ v | | +---------+ | | | |>---(D)-- Authorization Code ---------' | | Client | & Redirection URI | | | | | |<---(E)----- Access Token -------------------' +---------+ (w/ Optional Refresh Token)
授權碼授權開發
引入OAuth-server包
# PHP 5.3.9+
composer require bshaffer/oauth2-server-php "^1.10"
建立資料表
-- 你可使用相應的資料庫引擎:MySQL / SQLite / PostgreSQL / MS SQL Server -- 資料庫:oauth_test -- 細調過表相關結構,不過你也可以參考官方:http://bshaffer.github.io/oauth2-server-php-docs/cookbook/ DROP TABLE IF EXISTS `oauth_access_tokens`; CREATE TABLE `oauth_access_tokens` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `access_token` varchar(40) 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` text NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `IDX_ACCESS_TOKEN` (`access_token`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for oauth_authorization_codes -- ---------------------------- DROP TABLE IF EXISTS `oauth_authorization_codes`; CREATE TABLE `oauth_authorization_codes` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `authorization_code` varchar(40) DEFAULT '', `client_id` varchar(80) DEFAULT '', `user_id` varchar(80) DEFAULT '0', `redirect_uri` varchar(2000) DEFAULT '', `expires` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `scope` text, `id_token` varchar(1000) DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `IDX_CODE` (`authorization_code`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_authorization_codes -- ---------------------------- -- ---------------------------- -- Table structure for oauth_clients -- ---------------------------- DROP TABLE IF EXISTS `oauth_clients`; CREATE TABLE `oauth_clients` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `client_id` varchar(80) DEFAULT '', `client_secret` varchar(80) DEFAULT '', `client_name` varchar(120) DEFAULT '', `redirect_uri` varchar(2000) DEFAULT '', `grant_types` varchar(80) DEFAULT '', `scope` varchar(4000) DEFAULT '', `user_id` varchar(80) DEFAULT '0', PRIMARY KEY (`id`), KEY `IDX_APP_SECRET` (`client_id`,`client_secret`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_clients -- ---------------------------- INSERT INTO `oauth_clients` VALUES ('1', 'testclient', '123456', '測試demo', 'http://sxx.qkl.local/v2/oauth/cb', 'authorization_code refresh_token', 'basic get_user_info upload_pic', ''); -- ---------------------------- -- Table structure for oauth_jwt -- ---------------------------- DROP TABLE IF EXISTS `oauth_jwt`; CREATE TABLE `oauth_jwt` ( `client_id` varchar(80) NOT NULL, `subject` varchar(80) DEFAULT NULL, `public_key` varchar(2000) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_jwt -- ---------------------------- -- ---------------------------- -- Table structure for oauth_public_keys -- ---------------------------- DROP TABLE IF EXISTS `oauth_public_keys`; CREATE TABLE `oauth_public_keys` ( `client_id` varchar(80) DEFAULT NULL, `public_key` varchar(2000) DEFAULT NULL, `private_key` varchar(2000) DEFAULT NULL, `encryption_algorithm` varchar(100) DEFAULT 'RS256' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_public_keys -- ---------------------------- -- ---------------------------- -- Table structure for oauth_refresh_tokens -- ---------------------------- DROP TABLE IF EXISTS `oauth_refresh_tokens`; CREATE TABLE `oauth_refresh_tokens` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `refresh_token` varchar(40) NOT NULL, `client_id` varchar(80) NOT NULL DEFAULT '', `user_id` varchar(80) DEFAULT '', `expires` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `scope` text, PRIMARY KEY (`id`), UNIQUE KEY `IDX_REFRESH_TOKEN` (`refresh_token`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Table structure for oauth_scopes -- ---------------------------- DROP TABLE IF EXISTS `oauth_scopes`; CREATE TABLE `oauth_scopes` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `scope` varchar(80) NOT NULL DEFAULT '', `is_default` tinyint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_scopes -- ---------------------------- INSERT INTO `oauth_scopes` VALUES ('1', 'basic', '1'); INSERT INTO `oauth_scopes` VALUES ('2', 'get_user_info', '0'); INSERT INTO `oauth_scopes` VALUES ('3', 'upload_pic', '0'); -- ---------------------------- -- Table structure for oauth_users 該表是Resource Owner Password Credentials Grant所使用 -- ---------------------------- DROP TABLE IF EXISTS `oauth_users`; CREATE TABLE `oauth_users` ( `uid` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(80) DEFAULT '', `password` varchar(80) DEFAULT '', `first_name` varchar(80) DEFAULT '', `last_name` varchar(80) DEFAULT '', `email` varchar(80) DEFAULT '', `email_verified` tinyint(1) DEFAULT '0', `scope` text, PRIMARY KEY (`uid`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of oauth_users -- ---------------------------- INSERT INTO `oauth_users` VALUES ('1', 'qkl', '123456', 'kl', 'q', '', '', '');
建立server
Authorization Server 角色
public function _initialize() { require_once dirname(APP_PATH) . "/vendor/autoload.php"; Autoloader::register(); } private function server() { $pdo = new \PDO('mysql:host=ip;dbname=oauth_test', "user", "123456"); //建立儲存的方式 $storage = new \OAuth2\Storage\Pdo($pdo); //建立server $server = new \OAuth2\Server($storage); // 新增 Authorization Code 授予型別 $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($storage)); return $server; }
建立授權頁面(基於瀏覽器)
Authorization Server 角色
User Agent 角色,常規一般是三方登入授權頁面
// 授權頁面和授權
public function authorize()
{
// 該頁面請求地址類似:
// http://sxx.qkl.local/v2/oauth/authorize?response_type=code&client_id=testclient&state=xyz&redirect_uri=http://sxx.qkl.local/v2/oauth/cb&scope=basic%20get_user_info%20upload_pic
//獲取server物件
$server = $this->server();
$request = \OAuth2\Request::createFromGlobals();
$response = new \OAuth2\Response();
// 驗證 authorize request
// 這裡會驗證client_id,redirect_uri等引數和client是否有scope
if (!$server->validateAuthorizeRequest($request, $response)) {
$response->send();
die;
}
// 顯示授權登入頁面
if (empty($_POST)) {
//獲取client型別的storage
//不過這裡我們在server裡設定了storage,其實都是一樣的storage->pdo.mysql
$pdo = $server->getStorage('client');
//獲取oauth_clients表的對應的client應用的資料
$clientInfo = $pdo->getClientDetails($request->query('client_id'));
$this->assign('clientInfo', $clientInfo);
$this->display('authorize');
die();
}
$is_authorized = true;
// 當然這部分常規是基於自己現有的帳號系統驗證
if (!$uid = $this->checkLogin($request)) {
$is_authorized = false;
}
// 這裡是授權獲取code,並拼接Location地址返回相應
// Location的地址類似:http://sxx.qkl.local/v2/oauth/cb?code=69d78ea06b5ee41acbb9dfb90500823c8ac0241d&state=xyz
// 這裡的$uid不是上面oauth_users表的uid, 是自己系統裡的帳號的id,你也可以省略該引數
$server->handleAuthorizeRequest($request, $response, $is_authorized, $uid);
// if ($is_authorized) {
// // 這裡會建立Location跳轉,你可以直接獲取相關的跳轉url,用於debug
// $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=')+5, 40);
// exit("SUCCESS! Authorization Code: $code :: " . $response->getHttpHeader('Location'));
// }
$response->send();
}
/**
* 具體基於自己現有的帳號系統驗證
* @param $request
* @return bool
*/
private function checkLogin($request)
{
//todo
if ($request->request('username') != 'qkl') {
return $uid = 0; //login faile
}
return $uid = 1; //login success
}
建立獲取token
Authorization Server 角色
// 生成並獲取token
public function token()
{
$server = $this->server();
$server->handleTokenRequest(\OAuth2\Request::createFromGlobals())->send();
exit();
}
授權頁面
CLIENT 客戶端 角色
# 瀏覽器訪問:
http://sxx.qkl.local/v2/oauth/authorize?response_type=code&client_id=testclient&state=xyz&redirect_uri=http://sxx.qkl.local/v2/oauth/cb&scope=basic%20get_user_info%20upload_pic
授權頁面說明
# 我們換行分解下
http://sxx.qkl.local/v2/oauth/authorize?
# response_type 固定寫死 code
response_type=code&
# client_id 我們oauth_clients表的client_id值
client_id=testclient&
# state 自定義的引數,隨意字串值
state=xyz&
# redirect_uri 回撥地址,這裡最好是urlencode編碼,我這裡演示沒編碼
# 注意這裡的redirect_uri需要和oauth_clients表的redirect_uri欄位做匹配處理
# redirect_uri欄位可存取的方式:
# 1. http://sxx.qkl.local/v2/oauth/cb
# 2. http://sxx.qkl.local/v2/oauth/cb http://sxx.qkl.local/v2/oauth/cb2 ... 空格分割
redirect_uri=http://sxx.qkl.local/v2/oauth/cb&
# response_type 固定寫死 code
scope=basic%20get_user_info%20upload_pic
客戶端獲取code並請求獲取access_token
CLIENT 客戶端 角色
// 客戶端回撥,來自server端的Location跳轉到此
// 此處會攜帶上code和你自定義的state
public function cb()
{
$request = \OAuth2\Request::createFromGlobals();
$url = "http://sxx.qkl.local/v2/oauth/token";
$data = [
'grant_type' => 'authorization_code',
'code' => $request->query('code'),
'client_id' => 'testclient',
'client_secret' => '123456',
'redirect_uri' => 'http://sxx.qkl.local/v2/oauth/cb'
];
//todo 自定義的處理判斷
$state = $request->query('state');
$response = Curl::ihttp_post($url, $data);
if (is_error($response)) {
var_dump($response);
}
var_dump($response['content']);
}
重新整理token
Authorization Server 角色
// 建立重新整理token的server
private function refresh_token_server()
{
$pdo = new \PDO('mysql:host=ip;dbname=oauth_test', "user", "123456");
$storage = new \OAuth2\Storage\Pdo($pdo);
$config = [
'always_issue_new_refresh_token' => true,
'refresh_token_lifetime' => 2419200,
];
$server = new \OAuth2\Server($storage, $config);
// 新增一個 RefreshToken 的型別
$server->addGrantType(new \OAuth2\GrantType\RefreshToken($storage, [
'always_issue_new_refresh_token' => true
]));
// 新增一個token的Response
$server->addResponseType(new \OAuth2\ResponseType\AccessToken($storage, $storage, [
'refresh_token_lifetime' => 2419200,
]));
return $server;
}
// 重新整理token
public function refresh_token()
{
$server = $this->refresh_token_server();
$server->handleTokenRequest(\OAuth2\Request::createFromGlobals())->send();
exit();
}
客戶端請求refresh_token
CLIENT 客戶端 角色
// 客戶端模擬refresh_token
public function client_refresh_token()
{
$request = \OAuth2\Request::createFromGlobals();
$url = "http://sxx.qkl.local/v2/oauth/refresh_token";
$data = [
'grant_type' => 'refresh_token',
'refresh_token' => 'd9c5bee6a4ad7967ac044c99e40496aa2c3d28b4',
'client_id' => 'testclient',
'client_secret' => '123456'
];
$response = Curl::ihttp_post($url, $data);
if (is_error($response)) {
var_dump($response);
}
var_dump($response['content']);
}
scope授權資源
Authorization Server 角色
這裡說明下 因為在上面表建立時,我建立了3個socpe[basic,get_user_info,upload_pic]用於測試上面我們在瀏覽器訪問的授權地址上也填寫了三個許可權,所以只要access_token正確在時效內,即可成功訪問
// 測試資源
public function res1()
{
$server = $this->server();
// Handle a request to a resource and authenticate the access token
if (!$server->verifyResourceRequest(\OAuth2\Request::createFromGlobals())) {
$server->getResponse()->send();
die;
}
$token = $server->getAccessTokenData(\OAuth2\Request::createFromGlobals());
$scopes = explode(" ", $token['scope']);
// todo 這裡你可以寫成自己規則的scope驗證
if (!$this->checkScope('basic', $scopes)) {
$this->ajaxReturn(['success' => false, 'message' => '你沒有獲取該介面的scope']);
}
$this->ajaxReturn(['success' => true, 'message' => '你成功獲取該介面資訊', 'token'=>$token['user_id']]);
}
// 用於演示檢測scope的方法
private function checkScope($myScope, $scopes)
{
return in_array($myScope, $scopes);
}
客戶端postman模擬測試
正確的access_token請求:
錯誤或失效的access_token請求:
總結
Oauth2.0整體沒什麼具體的技術含量,可以參照規範實現即可