http://www.fising.cn/2011/03/%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5%E6%90%AD%E5%BB%BA-oauth-%E8%AE%A4%E8%AF%81%E6%9C%8D%E5%8A%A1%E5%99%A8.shtml
http://blog.csdn.net/newjueqi/article/details/7845282
http://www.open-open.com/lib/view/open1392863557428.html
http://justcoding.iteye.com/blog/1950270
http://oauth.net/documentation/getting-started/
https://code.google.com/archive/p/oauth-php/
Bug:
源碼包庫文件OAuthRequestSigner.php有bug
function getQueryString ( $oauth_as_header = true )
{
...
if ( !$oauth_as_header
|| (strncmp($name, 'oauth_', 6) != 0&&(應該為||) strncmp($name, 'xoauth_', 7) != 0))
現在越來越多開放的互聯網公司提供對外的 API 接口,使得第三方應用開發人員可以開發基於該平台接口的應用程序。國外有Twitter、Flicker Service等;國內的,像騰訊微博開放平台、新浪微博開放平台等等。
這些平台接口的認證方式,無一例外的,都采取了 OAuth 來實現(Twitter原來使用的是Basic Auth方式,后來全面轉向OAuth)。
那么,OAuth 是什么?OAuth認證又有什么好處呢?
OAuth 是什么
關於OAuth的定義,在 OAuth官網 的首頁上,有一行大大的文字說明:
1
|
An open protocol to allow secure API authorization in a simple and standard method from desktop and web applications.
|
(OAuth) 是一個開放協議,它以一種簡單、標准的方式實現對桌面和 Web 的應用程序的安全 API 認證。
OAuth 1.0 協議(中文版 | 英文版)這樣介紹OAuth:“OAuth 協議致力於使網站和應用程序(統稱為消費方)能夠在無須用戶透露其認證證書的情況下,通過 API 訪問某個web服務(統稱為服務提供方)的受保護資源。更一般地說,OAuth 為 API 認證提供了一個可自由實現且通用的方法”。
關於 OAuth 的用途,OAuth 1.0 協議(中文版 | 英文版)上舉了一個例子:某打印服務提供商 printer.example.com(消費方),希望在無須用戶提供其照片存儲站點密碼的情況下,訪問用戶儲存在 photos.example.net(服務提供方)上的個人照片。
假如沒有 OAuth,用戶必須要向消費方也就是 printer 提供自己在服務提供商 photos 上的授權資料(通常是密碼),消費方利用這個授權資料,通過服務提供方的權限驗證,從而獲得要打印的圖片。這樣看似沒有什么問題。但是用戶的授權資料,通常在這一過程中被消費方有意或者無意地竊取或泄露,從而對用戶和服務提供方的信息安全造成威脅。
而如果利用 OAuth 進行此過程的授權,用戶的授權資料並不會傳遞給第三方(也就是消費方,通常是App應用),而消費方只需要將用戶引導至服務提供方的授權頁面進行授權,使得消費方獲得訪問受限資源的權限即可。而在此過程中,用戶授權過程是在服務提供方進行的,消費方並不會直接接觸到用戶的授權資料,因此一般不會造成用戶授權資料的泄密,從而既保證了用戶和服務提供方的信息安全,又使得消費方完成了對受限資源的讀取。可謂一舉三得。
個人對 OAuth 授權過程的理解:服務提供方 SP 好比一個封閉院子,只有持卡人才能進入,用戶 U 就是持卡人之一。而消費方 C 沒有持卡,通常情況下是不能進入的。但是有一天,由於特殊原因,U 需要 C 幫忙去 SP 那里取一樣東西。這個時候問題就來了: C 沒有持卡,不能進去院子,而 U 又不能把卡直接給 C (卡上面有很多個人機密信息,不方便外泄哦)。怎么辦呢?
哦,對了,U 可以帶着 C 去門口,告訴SP:這個人是我認識的,他需要進去幫我拿我的一樣東西,請予放行。這樣,U 既不用將帶有個人私密信息的門卡交給 C,C 也通過驗證拿到了屬於 U 的東西。
有的人要問了,是不是下次 C 想要再進 SP 的拿 U 的東西的話,是不是就不用 U 的指引了呢?人類社會的情況通常是這樣的。可惜,在 HTTP 的世界里,由於 HTTP 是無狀態的協議,因此,SP 仍然不會認識 C。所以,每次 C 想要取東西,總是需要 U 的指引。是不是很麻煩呢?呵呵。但是為了安全,麻煩一點又有什何妨!
上面介紹了 OAuth 認證的基本思路,如果你還不理解,可以參考 OAuth認證流程圖, 或者查看騰訊微博關於OAuth認證的介紹。OAuth 官方網站就有一篇文檔教程《OAuth入門指南》,不過沒有中文版本。有興趣同學的也可以自己看看。
OAuth 認證授權有以下幾個特點:
- 1. 簡單:不管是 OAuth 服務提供者還是應用開發者,都很容易於理解與使用;
- 2. 安全:沒有涉及到用戶密鑰等信息,更安全更靈活;
- 3. 開放:任何服務提供商都可以實現 OAuth,任何軟件開發商都可以使用 OAuth;
那么下面我們就作為服務提供商角色,來實現 OAuth 認證服務器的安裝和搭建。
其實,很多先行者已經開發出了很多的 OAuth 消費方代碼(客戶端)和服務提供方代碼(服務端),這里是它們的一些列表,其中包含了 .NET (C#/VB.NET), ColdFusion, Java, Javascript, Jifty, Objective-C, OCaml, Perl, PHP, Python, Ruby, Erlang 和其他語言的一些實現。
通過 Google,我找到了一個開源的 OAuth 服務端代碼和消費方開源代碼庫項目——oauth-php. 我們就借此來實現。關於OAuth,項目主頁的介紹是: OAuth Consumer And Server Library For PHP. 它包含一個完整實現的可擴展的OAuth存儲,支持 MySQL/MySQLi, Postgresql, PDO 和 Oracle 等多種存儲方式。
它實現了以下方法:
- 認證進來的請求
- 為出去的請求簽名
- 使用 body 為請求簽名
- 為多用戶管理消費方的 key 和 token(服務端和消費端)
- 記錄經過類庫處理的進出的請求(可以在數據庫中進行可選配置)
很多網站都在使用oauth-php, 包括荷蘭阿姆斯特丹Mediamatic Lab實驗室出品的anyMeta CMS. 目前,oauth-php 的代碼主要由Corollarium Technologies 負責維護。
為你的服務器增加 OAuth 非常簡單。你需要檢查進來的請求中 OAuth 認證細節。首先,我們需要四個控制器 controller 文件:
- oauth_register.php 使消費方用戶獲得 key 和密鑰
- request_token.php 返回一個未認證的 request token
- authorize.php 認證一個request token
- access_token.php 將認證后的 request token 置換為 access token
以下的例子,假設使用的數據存儲器是MySQL。你也可以使用其他數據庫——當你第一次使用 OAuthStore 實例的時候指定一個參數,告訴它你要使用的數據庫。
1
|
$store = OAuthStore::instance('mystore');
|
這就是假設在存儲器目錄,你有一個名為 OAuthStoremystore.php 文件。在這里我們使用 OAuthStoreMySQL.php 文件來實現。這也就是說,我們在實例化 OAuthStore 的時候,需要這樣做:
1
|
$store = OAuthStore::instance('MySQL');
|
然后,在每個請求被處理之前,都可以檢查其是否帶有 OAuth 認證信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
if (OAuthRequestVerifier::requestIsSigned())
{
try
{
$req = new OAuthRequestVerifier();
$user_id = $req->verify();
// 如果存在 user_id, 那么作為那個用戶角色登錄(對於本次請求)
if ($user_id)
{
// **** 在這里新增你的代碼 ****
}
}
catch (OAuthException $e)
{
// 請求已經簽名,但是認證失敗
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: OAuth realm=""');
header('Content-Type: text/plain; charset=utf8');
echo $e->getMessage();
exit();
}
}
|
每個消費方都使用 key 和密鑰的組合和 token 和 token 密鑰的組合來為它的請求進行簽名。而在此之前,消費方必須要先獲取屬於它的消費方 key 和消費方密鑰。 oauth_register.php 就是負責分發消費方 key 和密鑰的控制器文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// 當前登錄用戶
$user_id = 1;
// 下面的內容應該來自用戶填寫的表單
$consumer = array(
// 下面兩個是必須的
'requester_name' => 'John Doe',
'requester_email' => 'john@example.com',
// 下面的是可選的
'callback_uri' => 'http://www.myconsumersite.com/oauth_callback',
'application_uri' => 'http://www.myconsumersite.com/',
'application_title' => 'John Doe\'s consumer site',
'application_descr' => 'Make nice graphs of all your data',
'application_notes' => 'Bladibla',
'application_type' => 'website',
'application_commercial' => 0
);
// 注冊消費方
$store = OAuthStore::instance();
$key = $store->updateConsumer($consumer, $user_id);
// 從數據存儲器獲得完整的消費方信息
$consumer = $store->getConsumer($key);
// 消費方用戶將需要 key 和 secret
$consumer_id = $consumer['id'];
$consumer_key = $consumer['consumer_key'];
$consumer_secret = $consumer['consumer_secret'];
|
如果你想要更新之前注冊的消費方身份,提供消費方id,key 和 secret 。key 和 secret 在更新操作完成之前不會進行改變。
你還可以請求一個特定用戶注冊的所有消費方列表:
1
2
3
4
5
6
|
// 當前登錄用戶
$user_id = 1;
// 取得這個用戶注冊的全部消費方列表
$store = OAuthStore::instance();
$list = $store->listConsumers($user_id);
|
request_token.php 這個控制器文件負責返回請求 token(未認證的 request token)。當消費方獲取了 key 和 secret 之后,它就可以向服務器端請求未認證的 request token 了:
1
2
3
|
$server = new OAuthServer();
$token = $server->requestToken();
exit();
|
authorize.php 控制器文件負責認證一個用戶請求 token。這個控制器負責詢問用戶是否允許消費方訪問他的賬戶。如果允許,那么消費方將可以使用 request token 換取 access token。必須要保證用戶在訪問下面的代碼之前是登錄狀態。OAuthServer 服務器使用 SESSION 存儲一些 OAuth 狀態信息,所以必須要開啟seesion會話(要么是 session_start 函數,要么就是自動開啟)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// 當前登錄用戶
$user_id = 1;
// 取得OAuth存儲器和OAtuh服務器對象
$store = OAuthStore::instance();
$server = new OAuthServer();
try
{
// 檢查當前請求中是否包含合法的request token
// 返回一個包含消費方key, 消費方secret, token, token secret 和 token 類型的數組.
$rs = $server->authorizeVerify();
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
// 檢查用戶是否點擊了 'allow' 按鈕或者其他你指定的按鈕)
$authorized = array_key_exists('allow', $_POST);
// 設置 request token 的認證狀態(已認證或者是未認證)
// 當包含 oauth_callback 回調的時候,這些將傳給消費方
$server->authorizeFinish($authorized, $user_id);
// 沒有 oauth_callback 回調, 顯示認證結果
// ** 你的代碼 **
}
}
catch (OAuthException $e)
{
// 沒有需要認證的 request token, 顯示一個可以輸入 request token 的頁面
// ** 你的代碼 **
}
|
access_token.php 控制器文件負責將認證的request token換成access token。access token 可以被用來為請求簽名。
1
2
|
$server = new OAuthServer();
$server->accessToken();
|
看完本文,如果還是一頭霧水,不知道如何下手,可以繼續閱讀《基於PHP & MySQL 搭建 OAuth Server》。