爲了方便理解,可以先看一下在 OAuth 認證過程中的幾個關鍵術語,這也是 RFC5849 中 “1.1. Terminology” 小節的內容。也可以查看其中文版本。
想了一下,沒有想到好的應用場景,乾脆就使用 RFC5849 中的例子吧。這個例子大概的意思是:
1 2 3 4 5 | Jane (用戶,資源的所有者) 將自己度假的照片 (受保護資源) 上傳到了圖片分享網站A (服務提供方). 她現在想要在另外一個網站B (Client, 消費方) 在線打印這些照片. 一般情況下, Jane 需要使用自己的用戶名和密碼登陸網站A. 但是, Jane 並不希望將自己的用戶名和密碼泄露給網站B. 可是網站B需要訪問圖片分享網站A的圖片並將其打印出來. |
首先,我們再虛擬機上面搭建三個虛擬主機。我這裏搭建的三個主機是:
1 2 3 4 5 6 7 8 | # 服務提供方 Service Provider 服務提供服務器, 提供受保護資源 www.service.com # 服務提供方 Service Provider OAuth認證服務器,進行請求認證 auth.service.com # 消費方 Consumer 客戶應用服務器, 用來發起認證請求 www.demo.com |
配合上面介紹的應用場景,www.service.com 相當於網站A,而 www.demo.com 則相當於網站B.
接下來,我們爲網站 A 虛擬一個用戶 Jane,並將其用戶名和密碼以及她的照片保存在 MySQL 數據庫中。
先創建一個數據庫,名曰:photo, 在其中新建一個表user:
1 2 3 4 5 6 7 8 | CREATE DATABASE `photo`; CREATE TABLE IF NOT EXISTS ` user ` (
`userId` int (11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用戶ID' ,
`userName` varchar (20) NOT NULL COMMENT '用戶名' ,
` password ` char (32) NOT NULL COMMENT '會員密碼' ,
PRIMARY KEY (`userId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT= '用戶信息表' AUTO_INCREMENT=1 ; |
用戶有了,現在給用戶創建一個表,用來存儲用戶照片。新建一個表“image”:
1 2 3 4 5 6 7 | CREATE TABLE IF NOT EXISTS `image` (
`imageId` int (11) unsigned NOT NULL AUTO_INCREMENT COMMENT '圖片Id' ,
`userId` int (11) unsigned NOT NULL COMMENT '用戶Id' ,
`imagePath` varchar (255) NOT NULL COMMENT '圖片路徑' ,
PRIMARY KEY (`imageId`),
KEY `userId` (`userId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT= '圖片表' AUTO_INCREMENT=1 ; |
數據表有了,現在填充一些數據:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | INSERTINTO`photo`.` user ` (
`userId` ,
`userName` ,
` password ` ) VALUES (
'1' , 'jane' , MD5( '123456' ) ); INSERTINTO`photo`.`image` (
`imageId` ,
`userId` ,
`imagePath` ) VALUES (
NULL , '1' , 'path/to/jane/image.jpeg' ); |
由於 auth.service.com 認證服務器需要提供應用程序認證服務,所以需要創建一個表存儲應用程序信息。實際上,還需要一些其他的相關的數據表。
我們這裏使用的是 MySQL 數據庫,打開瀏覽器,訪問 http://auth.service.com/oauth-php/library/store/mysql/install.php 來進行數據表的安裝。事先需要編輯 install.php 進行數據庫配置。安裝完畢,請將該文件的數據庫連接部分重新註釋掉。
下面來實現OAUTH服務器端的應用註冊功能。
首先在 oauth.service.com 服務器下新建一個 config.inc.php 文件,文件內容如下:
1 2 3 4 5 6 7 8 9 | <?php // 數據庫連接信息 $dbOptions = array (
'server' => 'localhost' ,
'username' => 'root' ,
'password' => '123456' ,
'database' => 'photo' ); ?> |
該文件的主要作用是保存數據庫連接信息。然後繼續新建一個 “oauth_register.php” 文件,文件內容如下:
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 33 34 35 36 37 38 39 40 | <?php // 當前登錄用戶 $user_id = 1; // 來自用戶表單 $consumer = array (
// 下面兩項必填
'requester_name' => 'Fising' ,
// 以下均爲可選
'callback_uri' => 'http://www.demo.com/oauth_callback' ,
'application_uri' => 'http://www.demo.com/' ,
'application_title' => 'Online Printer' ,
'application_descr' => 'Online Print Your Photoes' ,
'application_notes' => 'Online Printer' ,
'application_type' => 'website' ,
'application_commercial' => 0 ); include_once 'config.inc.php' ; include_once 'oauth-php/library/OAuthStore.php' ; // 註冊消費方 $store = OAuthStore::instance( 'MySQL' , $dbOptions ); $key = $store ->updateConsumer( $consumer , $user_id ); // 獲取消費方信息 $consumer = $store ->getConsumer( $key , $user_id ); // 消費方註冊後得到的 App Key 和 App Secret $consumer_id = $consumer [ 'id' ]; $consumer_key = $consumer [ 'consumer_key' ]; $consumer_secret = $consumer [ 'consumer_secret' ]; // 輸出給消費方 echo 'Your App Key: ' . $consumer_key ; echo '<br />' ; echo 'Your App Secret: ' . $consumer_secret ; ?> |
這時候,通過瀏覽器訪問:http://auth.service.com 就可以自動註冊一個應用(其實就是一個消費方Client)。並且將該應用的 App Key 和 App Secret呈現給你。下面 www.demo.com 站點將使用這2個字符串進行認證。所以現在先把這兩個值保存起來備用。
看到的頁面應該類似於:
1 2 | Your App Key: de94eb65317c0d7a00af1261fc26882c04df0f850 Your App Secret: 7769ae71e703509a92c7f3816a9268af |
這樣,消費方註冊功能就完成了。
接下來,消費方 www.demo.com 就可以使用這個 App Key 和 App Secret,向認證服務器請求未授權的 Request token 了。這一步需要做兩件事情:① 消費方 www.demo.com 向 OAuth Server 也就是 auth.service.com 請求未授權的 Request token;② OAuth Server 處理消費方的請求,生成並將未授權的 Request token 返回給消費方;
先來實現第②件任務。
在認證服務器 auth.service.com 的根目錄下新建一個文件”request_token.php”, 文件內容是:
1 2 3 4 5 6 7 8 9 10 11 | <?php include_once 'config.inc.php' ; include_once 'oauth-php/library/OAuthStore.php' ; include_once 'oauth-php/library/OAuthServer.php' ; $store = OAuthStore::instance( 'MySQL' , $dbOptions ); $server = new OAuthServer(); $server ->requestToken(); exit (); ?> |
現在認證服務器已經可以響應消費方請求“未授權的token”了。
這裏要特別注意一點,如果測試的時候,消費方和服務提供方在同一臺服務器,就像我現在的情況,三個服務器都在同一個虛擬機裏,那麼要注意,客戶端請求的時候,可能發生找不到主機的問題。爲啥?因爲服務器找不到虛擬的 auth.service.com 認證服務器。怎麼解決呢?
Windows下面,我們可以設置 hosts 主機表文件,使某一域名的指向某一固定IP地址。Linux下面也有類似的主機表文件,它的位置是 /etc/hosts,接下來如何做,這裏就不用講啦。
現在來實現第①件任務。
首先在消費方數據庫服務器將認證服務器添加到消費方的 OAuthStore 中。這裏的數據庫安裝方式與服務方相同,不再贅述。
然後,我們再消費方服務器 www.demo.com 的根目錄添加一個 “config.inc.php” 文件,保存消費方自己的數據庫連接信息。我這裏由於使用的是虛擬主機,恰好消費方和認證服務器的數據庫連接參數是一樣的。實際情況可能不是這樣。文件的內容與上面的類似:
1 2 3 4 5 6 7 8 9 | <?php // 數據庫連接信息 $dbOptions = array (
'server' => 'localhost' ,
'username' => 'root' ,
'password' => '123456' ,
'database' => 'photo' ); ?> |
然後,在消費方服務器根目錄繼續添加一個文件,add_server.php, 用來向消費方的數據庫中存儲認證服務器的信息。其實一般的,這個信息可能會直接寫在配置文件裏,不過,oauth-php提供了更加強大的數據庫的存儲方案而已。該文件的內容是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php include_once 'config.inc.php' ; include_once 'oauth-php/library/OAuthStore.php' ; $store = OAuthStore::instance( 'MySQL' , $dbOptions ); // 當前用戶的ID, 必須爲整數 $user_id = 1; // 服務器描述信息 $server = array (
'consumer_key' => 'de94eb65317c0d7a00af1261fc26882c04df0f850' ,
'consumer_secret' => '7769ae71e703509a92c7f3816a9268af' ,
'server_uri' => 'http://auth.service.com/' ,
'signature_methods' => array ( 'HMAC-SHA1' , 'PLAINTEXT' ),
'request_token_uri' => 'http://auth.service.com/request_token.php' ,
'authorize_uri' => 'http://auth.service.com/authorize.php' ,
'access_token_uri' => 'http://auth.service.com/access_token.php' ); // 將服務器信息保存在 OAuthStore 中 $consumer_key = $store ->updateServer( $server , $user_id ); ?> |
這樣,通過瀏覽器訪問一下該文件,http://www.demo.com/add_server.php, 服務器的相關信息就會被保存起來了。用於生產環節時,這裏可能是一個簡單的管理系統,可以用來管理認證服務器列表。注意,上面文件裏的 key 和 secret 正是我們之前在認證服務器 http://auth.service.com 註冊消費方應用時得到的。
有了認證服務器的相關信息,我們現在可以去獲取“未認證的token”了。在 www.demo.com 根目錄新建一個文件 index.php:
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 33 34 35 36 37 38 39 40 41 | <?php if (isset( $_GET [ 'req' ]) && ( $_GET [ 'req' ] == 1)){
include_once 'config.inc.php' ;
include_once 'oauth-php/library/OAuthStore.php' ;
include_once 'oauth-php/library/OAuthRequester.php' ;
$store = OAuthStore::instance( 'MySQL' , $dbOptions );
// 用戶Id, 必須爲整型
$user_id = 1;
// 消費者key
$consumer_key = '286cec927c4c5482e75d80759e9fdd8904df10e2f' ;
// 從服務器獲取未授權的token
$token = OAuthRequester::requestRequestToken( $consumer_key , $user_id );
var_dump( $token );
die (); } else { ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
<html xmlns= "http://www.w3.org/1999/xhtml" >
<head>
<meta http-equiv= "Content-Type" content= "text/html; charset=utf-8" />
<title>測試頁面</title>
</head>
<body>
<p>消費放測試頁面,點擊下面的按鈕開始測試</p>
<input type= "button" name= "button" value= "Click Me" id= "RequestBtn" />
<script type= "text/javascript" >
document.getElementById( 'RequestBtn' ).onclick = function (){
window.location = 'index.php?req=1' ;
}
</script>
</body>
</html> <?php } ?> |
現在,通過瀏覽器訪問 www.demo.com/index.php頁面,然後點擊頁面上的“Click Me”按鈕,開始向auth.service.com服務器請求“未授權的token”。如果最後結果顯示類似於:
1 | array(2) { ["authorize_uri"]=> string(37) "http://auth.service.com/authorize.php" ["token"]=> string(41) "dc8e8df797d9737b0acfe7a8b549005604df5e485" } |
那麼恭喜你,獲取“未授權的token”這一步,已經順利完成了。
接下來,根據 OAuth 驗證的流程,應該是重定向用戶瀏覽器到 auth.service.com 進行 token 授權。
在 auth.demo.com 服務器根目錄新建一個文件 authorize.php, 代碼如下:
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <?php session_start(); if ( empty ( $_SESSION [ 'authorized' ])) {
$uri = $_SERVER [ 'REQUEST_URI' ];
header( 'Location: /login.php?goto=' . urlencode( $uri ));
exit (); } include_once 'config.inc.php' ; include_once 'oauth-php/library/OAuthStore.php' ; include_once 'oauth-php/library/OAuthServer.php' ; //登陸用戶 $user_id = 1; // 取得 oauth store 和 oauth server 對象 $store = OAuthStore::instance( 'MySQL' , $dbOptions ); $server = new OAuthServer(); try {
// 檢查當前請求中是否包含一個合法的請求token
// 返回一個數組, 包含consumer key, consumer secret, token, token secret 和 token type.
$rs = $server ->authorizeVerify();
if ( $_SERVER [ 'REQUEST_METHOD' ] == 'POST' )
{
// 判斷用戶是否點擊了 "allow" 按鈕(或者你可以自定義爲其他標識)
$authorized = array_key_exists ( 'allow' , $_POST );
// 設置token的認證狀態(已經被認證或者尚未認證)
// 如果存在 oauth_callback 參數, 重定向到客戶(消費方)地址
$server ->authorizeFinish( $authorized , $user_id );
// 如果沒有 oauth_callback 參數, 顯示認證結果
// ** 你的代碼 **
}
else
{
echo 'Error' ;
} } catch (OAuthException $e ) {
// 請求中沒有包含token, 顯示一個使用戶可以輸入token以進行驗證的頁面
// ** 你的代碼 ** } ?> |
如果用戶未登錄,則要求先行登陸才能進行授權操作。