原文轉自 http://www.cnblogs.com/ldms/p/4565547.html
Yii 有很多 extension 可以使用,在查看了 Yii 官網上提供的與 OAuth 相關的擴展後,發現了幾個 OAuth2 的客戶端擴展,但是並沒有找到可以作為 OAuth2 Server 的擴展。因為 Yii 是組織良好的易於擴展的框架,所以完全可以整合其它的 PHP OAuth2 Server 實作方案。在 OAuth.net/2/ 官網上,提供了幾個 PHP 實作的 OAuth2 Server。這裡使用第一個 OAuth2-Server-php 來作為 Yii 框架的 OAuth2 Server 擴展,需要一些必要的整合操作,主要是編寫一個類別來接受 client 存取和頒發 access_token 等。
第一部分: 資料庫準備
OAuth2-Server-php 使用的資料庫結構採用Github 上的oauth2-server-php README.md 提供的表格結構(Schema),總共有五張表:
my ;
+--------------------------+
| Tables_in_oauth2 |
+-------------- ------------+
| oauth_access_token |
| oauth_authorization_code |
| oauth |
| user -----------+
5 rows in set (0.00 sec)
各表的名字說明了表中存取的內容,且表名可自訂,自訂位置為:OAuth2/Storage /Pdo.php 48行的config 陣列中,因為這裡採用的是mysql 資料庫,所以需要修改的是Pdo,若是採用其它的儲存方案,如Redis,則自行修改對應檔案即可。注意這裡的資料庫名稱是都是單數形式。
使用下列sql 語句建立這5個表,並新增一個測試client:
###################################################################################################################################################################################
### oauth2 tables
##############################
if exists `oauth_access_token`;
drop table if exists `oauth_authorization_code`;
drop table if exists `oauth_refesh_token`s 3op table exists `oauthfrem _token`s pideo; CREATE TABLE `oauth_client` (
`client_id` VARCHAR(80) NOT NULL,
`client_secret` VARCHAR(80) NOT NULL,
`redirect_uri` VARCHAR(2000) NOT NULL,
CREATE TABLE `oauth_access_token` (
`access_token` VARCHAR(40 ) NOT NULL,
`client_id` VARCHAR(80) NOT NULL,
`user_id` VARCHAR(255),
`expires` VARCHAR(255),
`expires` TIMEST)
CONSTRAINT access_token_pk PRIMARY KEY (access_token)
);
CREATE TABLE `oauth_authorization_code` (
`authorization_code` VARCHAR(40) NOT NULL,
),
`redirect_uri` VARCHAR(2000),
`expires` TIMESTAMP NOT NULL,
`scope` VARCHAR(2000),
CONSTRAINT auth_code_pk PRIMARY KEY ( CONSTRAINT) `oauth_refresh_token` (
`refresh_token` VARCHAR(40) NOT NULL,
`client_id ` VARCHAR(80) NOT NULL,
`user_id` VARCHAR(255),
`expires` TIMESTAMP NOT NULL,
Y KEY (refresh_token)
);
--
CREATE TABLE `user` (
`user_id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(255) NOT NULL,
CHAR(255),
`last_name ` VARCHAR(255),
CONSTRAINT user_pk PRIMARY KEY (user_id)
);
-- test data
INSERT INTO oauth_client (client_id, client_secret, redirect_uri)
VALUES ("test name)
VALUES ('rereadyou', '8551be07bab21f3933e8177538d411e43b78dbcc', 'bo', 'zhang');
第二部分: 認證方案及實現了這一點方案以及它們在本實作中的使用方式進行分別說面。
第一種認證方式: Authorization Code Grant (授權碼認證)
授權碼透過使用授權伺服器做為客戶端與資源擁有者的中介而取得。客戶端不是直接從資源擁有者請求授權,而是引導資源擁有者至授權伺服器(由RFC2616定義的使用者代理),授權伺服器之後引導資源擁有者帶著授權碼回到客戶端。
在引導資源擁有者攜帶授權碼返回用戶端前,授權伺服器會鑑定資源擁有者身分並取得其授權。由於資源擁有者只與授權伺服器進行身份驗證,所以資源擁有者的憑證不需要與客戶端分享。
授權碼提供了一些重要的安全益處,例如驗證客戶端身份的能力,以及向客戶端直接的訪問令牌的傳輸而非通過資源所有者的用戶代理來傳送它而潛在暴露給他人(包括資源所有者)。
授權碼授權類型用於取得存取令牌和刷新令牌並未機密用戶端進行了最佳化。由於這是一個基於重定向的流程,客戶端必須能夠與資源所有者的使用者代理(通常是網頁瀏覽器)進行互動並能夠接收來自授權伺服器的傳入請求(透過重定向)。
Authorization Code Grant 流程(又稱Web Server Flow) 請參閱如下:
+----------+
| Resource |🎠 |
+------- ---+
^
|
(B) +---------------+
| +--- -(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent +----(B)-- User authenticates --->| Server | |
| +----(C)-- Authorization Code --- | | (A) (C) | | |
| Client | & Redirection URI |
|
--------+ (w/ Optional Refresh Token)
注意:說明步驟(A)、(B)和(C)的直線因為透過使用者代理而分為兩部分。
圖1:授權碼流程
圖1所示的流程包括下列步驟:
(A)用戶端透過向授權端點引導資源擁有者的使用者代理程式開始流程。客戶端包括它的客戶端標識、請求範圍、本地狀態和重定向URI,一旦存取被許可(或拒絕)授權伺服器將傳送用戶代理回到該URI。
(B)授權伺服器驗證資源擁有者的身分(透過使用者代理),並決定資源擁有者是否授予或拒絕客戶端的存取請求。
(C)假設資源擁有者許可訪問,授權伺服器使用之前(在請求時或客戶端註冊時)提供的重定向URI重定向用戶代理回到客戶端。重定向URI包括授權碼和先前客戶端提供的任何本地狀態。
(D)用戶端透過包含上一個步驟中收到的授權碼從授權伺服器的令牌端點請求存取令牌。當發起請求時,客戶端與授權伺服器進行身份驗證。客戶端包含用於獲得授權碼的重定向URI來用於驗證。
(E)授權伺服器對客戶端進行身份驗證,驗證授權代碼,並確保接收的重定向URI與在步驟(C)中用於重定向客戶端的URI相匹配。如果通過,授權伺服器回應傳回存取權杖與可選的刷新令牌。
流程實作:
1. client app 使用app id 取得authorization code:
www.yii.com/oauth2/index.
www.yii.com/oauth2/index.
www.yii.com/oauth2/index。
返回:$authcode = authorization code.
Tips: authorization code will expired in 30s,可修改OAuth2/ResponseType/AuthorizationCode.php 中的AuthorizationCode class 的建構方法配置參數來自訂authorization_code 有效時間。
client_id 是先前註冊在本 Server 上的應用程式名稱,這屬於用戶端管理範疇。
這一步驟需要進行使用者(資源擁有者)登入 OAuth2 Server 來完成授權作業。使用者登入屬使用者管理範疇,不屬 OAuth2 Server 中應編寫的功能。
使用者登入後可選擇可至 client app 開放的作業(授權)。
這一步驟綁定過程中,從安全角度來考慮應強制使用者重新輸入使用者名稱密碼確認綁定,不要直接讀取目前使用者session進行綁定。
2. 取得 access_token:
client app 使用 authorization code 換取 access_token
curl -u testclient:testpass www.yii.com/oauth2/index.php?r=oauth2/token -d "grant_type=authorization_code&code=$authcode
ken":"aea4a1059d3194a3dd5e4117bedd6e07ccc3f402",
"expires_in":3600,
"token_type":"bearer",
"scope":null,
"refresh_token":"269a623f54171e8598b1852eefcf115f4882b820"
}
失敗:
{"error":"invalid_grant",
"error_description" :"Authorization code doesn't exist or is invalid for the client"
}
Tip: 本步驟中需要使用客戶端的client_id 和access_tokne 有效期限為3600s, refresh_token 有效期限為1209600s,可在OAuth2/ResponseType/AccessToken.php 的AccessToken class 中的建構函式設定中進行修改。
卡(它不支援發行刷新令牌),並對知道操作特定重定向URI的公共用戶端進行最佳化。導向的流程,用戶端必須能夠與資源擁有者的使用者代理程式(通常是Web瀏覽器)進行互動並能夠接收來自授權伺服器的傳入請求(透過重新導向)。存取權杖的授權碼許可類型,用戶端收到存取權杖作為授權請求的結果。
隱式授權類型不包含用戶端身分驗證而依賴資源擁有者在場和重新導向URI的註冊。因為存取權杖被編碼到重定向URI中,它可能會暴露給資源所有者和其他駐留在相同裝置上的應用程式。
採用Implicit Grant方式取得Access Token的授權驗證流程又稱為User-Agent Flow,適用於所有無Server端配合的應用(由於應用往往位於一個User Agent裡,如瀏覽器裡面,因此這類應用器裡面,因此這類應用器在某些平台下又被稱為Client-Side Application),如手機/桌面客戶端程式、瀏覽器插件等,以及基於JavaScript等腳本客戶端腳本語言實現的應用,他們的一個共同特點是,應用無法妥善保管其應用程式金鑰(App Secret Key),如果採取Authorization Code模式,則會有洩漏其應用金鑰的可能性。其流程示意圖如下:
+----------+
| source |
| Owner ^
|
(B )
+----|-----+ Client Identifier +---------------+
| --+
| ->| |
| User- | |
| Agent |----(B)-- User authenticates -->| Server |
| | |
- Redirection URI ---- | | ----(D)--- 重新導向URI ---->| 網路託管|
| | | ------------ | +-------------+
+-|------- -+
| |
(A) (G) 存取令牌
| |
| 顧客|
| |
+-- |
+-- ---+
註:說明步驟(A)和(B)的直線經由使用者代理而分成兩部分。
圖2:隱式許可流程
圖2所示的流程包含以下步驟:
(A)用戶端透過向授權端點引導資源擁有者的使用者代理程式啟動流程。客戶端包括其客戶端標識、請求範圍、本機狀態和重定向URI ,一旦存取被許可(或拒絕)授權伺服器會將使用者代理程式傳回該URI。
(B)授權伺服器驗證資源擁有者的身分(透過使用者代理),並決定資源擁有者是否接受或拒絕客戶端的存取要求。
(C)假設資源擁有者許可訪問,授權伺服器使用之前(在請求時或客戶端註冊時)提供重定向URI用戶代理客戶端返回。重定向URI在URI片段中包含存取權杖。
(D)使用者代理程式順著方向指示向Web託管的用戶端資源發起請求(按RFC2616該請求不包含片段)。用戶代理在本地保留片段資訊。
(E)Web託管的客戶端資源返回一個網頁(通常是帶有嵌入式腳本的HTML 文件),該網頁能夠包含訪問用戶代理、保留片段的完整重定向URI 並提取包含在片段中參數的訪問令牌(和)。
其他 (F )使用者代理程式在本地執行Web託管的用戶端資源提供提取存取權杖的腳本。
(G)使用者代理程式傳送存取令牌給客戶端。
Tips: 1.一般不需要提供client_secret,另外client_id ,單一使用者同樣需要認證。
2. 隱式授權類型不支援refresh_token(或可自行實現)機制。
3. 使用者第一次使用隱式授權流程對您的應用程式進行身份驗證時儲存存取權杖!獲得訪問令牌後,請勿嘗試重新進行身份驗證。您儲存的存取令牌應該繼續有效!
一旦取得access_token(存在於redirect_uri的fragment中,即uri中的#部分),客戶端需要自行儲存access_token。
4.比較適用於用戶端應用程序,例如手機/桌面用戶端程式、瀏覽器外掛程式等
oauth2-server-php 對此授權方式的實作如下:
1. 此授權方式包含於 Authorization Code Grant (是 Authorization Code Grant 方式的簡化)。
初始化OAuth2Controller 時, 只需新增AuthorizationCode 類型的授權即可,如下:
$server->addGrantType(newstor. Code 預設不支援Implicit Grant, 需要將Server. php 第104 行的'allow_implicit' 修改為'true' 開啟Implicit 授權。
2. 取得access_token
http://www.yii.com/oauth2/index.php?r=oauth2/authorize&response_type=token&client_id=testclient&client_id=testcli. response_type=token (必須,固定值)
client_id (必須)
redirect_uri 選 state 建議
注意:response_type = token 而非code, 因為隱式授權不用取得authorization code。
返回:
成功:
使用者先點選授權按鈕。
SUCCESS! Authorization Code: www.baidu.com?#access_token=9f0c38b475e51ccd3
{"error":"redirect_uri_mismatch","error_description":"The redirect URI provided is missing or does not match"," 2"}
access_token 存在於redirect_uri 中的片段(fragment)中, 即'#'符號之後,client 需要自己提取片段中的access_token 並注意保存。開發人員應注意,有些使用者代理程式不支援在HTTP「Location」HTTP回應標頭欄位中包含片段組成部分。這些用戶端需要使用除了3xx 重定向回應以外的其他方法來重定向客戶端——-例如,返回一個HTML頁面,其中包含一個具有連結到重定向URI的動作的「繼續」按鈕。
第三種認證方式: Resource Owner Password Credentials (資源擁有者密碼憑證許可)
資源擁有者密碼憑證許可類型適合於資源擁有者與客戶端具有作業系統關係的情況,例如裝置或高階系統關係特權應用。當啟用這種許可類型時授權伺服器應該特別關照且只有當其他流程都不可用時才可以。
這種授權類型適合能夠取得資源擁有者憑證(使用者名稱和密碼,通常使用互動的形式)的客戶端。透過轉換已儲存的憑證至存取權令牌,它也用於遷移現存的使用如HTTP基本或摘要身份驗證的直接身份驗證方案的用戶端至OAuth。
+----------+
| Resource |
| Owner |
v
| Resource Owner
(A) Password Credentials
|
v
與 +---------+ +---------------+
| |>--(B)---- Resource Owner -- ----->| |
| | Client | )---- Access Token --------- |
+--- ------+ 步驟:
(A)資源擁有者提供給客戶端它的使用者名稱和密碼。
(B)透過包含從資源擁有者接收到的憑證,用戶端從授權伺服器的令牌端點請求存取令牌。當發起請求時,客戶端與授權伺服器進行身份驗證。
(C)授權伺服器對用戶端進行身份驗證,驗證資源擁有者的憑證,如果有效,請頒發存取權杖。
Tips: 客戶端一旦取得存取令牌必須丟棄憑證。
oauth2-服務器new OAuth2GrantTypeUserCredentials($storage));
2. 取得access_token :
curl -u testclient:testpass www.yii.com/oauth2/index. '
回:
{"access_token":"66decd1b10891db5f8f63efe7 "token_type":"bearer",
"scope":null,
Tips : Github 上oauth2-server-php 提供的sql schema user 表裡面沒有user_id 欄位[12],需要自行加入該欄位(主鍵, auto_increment)。
user 表設計使用 sha1 摘要方式,並未新增 salt。
在Pdo.php 有:
// plaintext password , $password)
{
return $user['password'] == sha1($password );
}
使用者認證時需要改寫此函數。
第四種認證方式: Client Credentials Grant (客戶端憑證許可)
當客戶端請求存取它所控制的,或事先與授權伺服器協商(所採用的方法超出了本規範的範圍)的其他資源擁有者的受保護資源,用戶端可以只使用它的客戶端憑證(或其他受支援的身份驗證方法)請求存取權杖。
用戶端憑證授權類型必須只能由機密用戶端使用。
+---------+ 卷 | Server |
| | | |
+---------+
圖4:客戶端憑證流程
在圖4中的所示流程包含以下步驟:
(A)用戶端與授權伺服器進行身份驗證並向令牌端點請求存取令牌。
(B)授權伺服器對用戶端進行身份驗證,如果有效,請頒發存取權杖。
Tips: 這是最簡單的認證方式。
因客戶端身分驗證被用作授權許可,因此不需要其他授權請求。
實作如下:
1. 在Oauth2Controller 2. 取得access_token:
curl -u testclient: testpass www.yii.com/oauth2/index.php?r=oauth2/token -d 'grant_type=client_credentials'
提交參數: gr scope OPTIONAL.
回:
{"access_token": "f3c30def0d28c633e34921b65388eb0bbd9d5ff9",
"expires_in":3600,): "scope":null}
Tips: Client 直接使用自己的client id 和client_secret 取得access_token;
RFC67499規範指明[10] clinet crendentials 用戶端認證取得access_token 時不包括refresh_token。
不過,oauth2-server-php 提供了控制開關,在 OAuth2/GrantTypes/ClientCredentials.php 第 33 行[11],
acc 頒發 refresh_token。
第三部分: access_token 類型說明
用戶端在操作資料資源時(透過 api)需要向 server 出示 access_token,關於如何出示 access_token 和 access_token 類型由下列部分說明。
IETF rfc 6749 中所說明的 access_token 類型有兩種:Bearer type 和 MAC type。
由於 OAuth2-Server-php 對於 MAC 類型的 access_token 尚在開發之中,以下僅針對最常使用的 Bearer 類型 access_token 進行說明。
有三種在資源請求中傳送 bearer access_token 資源給資源伺服器的方法[13]。客戶端不能在每次請求中使用超過一個方法傳輸令牌。
a. 當在由HTTP/1.1[RFC2617]定義的「Authorization」請求頭部欄位中傳送存取權杖時,用戶端使用「Bearer」驗證方案來傳送存取權杖。
GET /resource HTTP/1.1
Host: server.example.com
Authorization: 5091. 用戶端應該使用帶有「Bearer」HTTP授權方案的「Authorization」請求頭部欄位發起帶有不記名令牌的身份驗證請求。資源伺服器必須支援此方法。
b. 表單編碼的主體參數
當在HTTP請求實體主體傳送存取權杖時,且客戶端採用「access_token」參數向請求主體中新增存取令牌。用戶端不能使用此方法,除非符合下列所有條件:
HTTP請求的實體頭部含有設定為「application/x-www-form-urlencoded」的「Content-Type」頭部欄位。
實體主體遵循HTML4.01[W3C.REC-html401-19991224]定義的「application/x-www-form-urlencoded」內容類型的編碼要求。
HTTP請求實體主體是單一的部分。
在實體主體中編碼的內容必須完全由ASCII[USASCII]字元組成。
HTTP請求方法是請求主體定義為其定義的語法。尤其是,這意味著“GET”方法不能被使用。
客戶使用傳輸層安全發動如下的HTTP請求:
.com
Content-Type: application/x-www-form-urlencoded
access_token=mF_9.B5f -4.1JqM
c. 當在HTTP請求URI中發送存取權杖時,用戶端採用「access_token」參數,向「統一資源標示符(URI):通用語法」RFC3986定義的請求URI查詢部分新增存取命令牌。
例如,客戶端採用傳輸層安全啟動如下的HTTP請求:
GET /resource?access_token=mF_9.B5f-4.1JJJJJM未來化
它不應該被使用,除非不能在「Authorization」請求頭部欄位或HTTP請求實體主體中傳送存取權杖。
以上在 rfc6750 規格所提出的三種 access_token 的使用方式。推薦使用第一種方案。 Bearer token 的使用需要藉助 TLS 來確保 access_token 傳輸時的安全性。
第四部分: 使用Bearer access_token 的呼叫api
1. 使用refresh_token testaccess_token:
curl -u testcliclient:testpass/ynii.com =refresh_token&refresh_token=1ce1a52dff3b5ab836ae25714c714cb86bf31b6f"
返回:
25f64da18f1",
"expires_in":3600,
"token_type": 這裡沒有新的refresh_token,需要進行設定以重新取得refresh_token,可修改OAuth2/GrantType/RefreshToken.php 中的RefreshToken class __construct 方法中的'always_issue_new_refresh_token' => true 來開啟頒發新的refresh_token。
Tips: IETF rfc2649 中關於refresh_token section 的部分說明,
POST /token HTTP/1.1 czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
需要提供客戶端的client_id 和client_secret, grant_type 值必須是refresh_token。
access_token 有效期限內不能使用 refresh_token 換取新的 access_token。
2. 使用 access_token:
a. client app 使用 access_token 取得 resource 資訊。
oauth2-server 驗證 access_token:
curl www.yii.com/oauth2/index.php?r=oauth2/verifytoken -d 'access_token=aea4a105953194670747000cc
回:
{"result":"success",
"message":"your access token is valid."
} 此部分只是為了驗證access token 的有效性,client app 並不應該直接調用該方法,而是在請求資源時有server不同處理。
可以在 Oauth2 extension 的 Server.php 中來修改 access_token 的有效期限。
3. scope
scope 需要服務端確定特定的可行操作。
scope 用來決定 client 所能進行的操作權限。專案中操作權限由 srbac 控制, Oauth2 中暫不做處理。
4. state
state 為 client app 在第一步驟中取得 authorization code 時傳遞並由 OAuth2 Server 傳回的隨機雜湊參數。 state 參數主要用來防止跨站點請求偽造(Cross Site Request Forgery, CSRF),相關討論可參考本文最後的參考【7】和【8】。
References: