現在越來越多開放的互聯網公司提供對外的 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 認證資訊:
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 和金鑰的控制器檔。
$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 在更新操作完成之前不會進行改變。
你還可以請求一個特定使用者註冊的所有消費方清單:
$user_id = 1;
// 取得這個使用者註冊的全部消費方清單
$store = OAuthStore::instance();
$list = $store->listConsumers($user_id);
request_token.php 這個控制器檔負責返回請求 token(未認證的 request token)。當消費方獲取了 key 和 secret 之後,它就可以向伺服器端請求未認證的 request token 了:
$server = new OAuthServer();
$token = $server->requestToken();
exit();
authorize.php 控制器檔負責認證一個使用者請求 token。這個控制器負責詢問使用者是否允許消費方訪問他的帳戶。如果允許,那麼消費方將可以使用 request token 換取 access token。必須要保證使用者在訪問下面的代碼之前是登錄狀態。OAuthServer 伺服器使用 SESSION 存儲一些 OAuth 狀態資訊,所以必須要開啟seesion會話(要麼是 session_start 函數,要麼就是自動開啟)。
$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 可以被用來為請求籤名。
$server = new OAuthServer();
$server->accessToken();
留言列表