基於PHP構建OAuth 2.0 認證平臺

各大門戶都推出了三方API,如Google,Facebook,QQ,Sina,Alibaba等等,本文來探討一下如何爲咱們的項目部署OAuth的問題。本文主要參考Fising兄的文章,說的更直白點,基本從Fising兄那裏抄襲而來,也算是給本身作個記錄,但願Fising兄看到後,不要介意。另外,您的文章的確寫的很是好,讓人敬佩。javascript

至於爲何要使用oauth呢,fising兄作了一個很是恰當的比喻:php

服務提供方 SP 比如一個封閉院子,只有持卡人才能進入,用戶 U 就是持卡人之一。而消費方 C 沒有持卡,一般狀況下是不能進入的。可是有一天,因爲特殊緣由,U 須要 C 幫忙去 SP 那裏取同樣東西。這個時候問題就來了: C 沒有持卡,不能進去院子,而 U 又不能把卡直接給 C (卡上面有不少我的機密信息,不方便外泄哦)。怎麼辦呢?html

哦,對了,U 能夠帶着 C 去門口,告訴SP:這我的是我認識的,他須要進去幫我拿個人同樣東西,請予放行。這樣,U 既不用將帶有我的私密信息的門卡交給 C,C 也經過驗證拿到了屬於 U 的東西。java

有的人要問了,是否是下次 C 想要再進 SP 的拿 U 的東西的話,是否是就不用 U 的指引了呢?人類社會的狀況一般是這樣的。惋惜,在 HTTP 的世界裏,因爲 HTTP 是無狀態的協議,所以,SP 仍然不會認識 C。因此,每次 C 想要取東西,老是須要 U 的指引。是否是很麻煩呢?呵呵。可是爲了安全,麻煩一點又有什何妨!mysql

一些官方的比喻是:android

Jane (用戶,資源的全部者) 將本身度假的照片 (受保護資源) 上傳到了圖片分享網站A (服務提供方).她如今想要在另一個網站B (Client, 消費方) 在線打印這些照片. 通常狀況下, Jane 須要使用本身的用戶名和密碼登錄網站A.可是, Jane 並不但願將本身的用戶名和密碼泄露給網站B. 但是網站B須要訪問圖片分享網站A的圖片並將其打印出來.web

準備工做:sql

1 PHP + MYSQL 環境
2 libcurl的支持(由於oauth-php裏面使用到了curl庫)
3 OAuth-PHP項目代碼數據庫

因爲我使用的是xampp環境,第一項知足了,第二項只須要在php.ini文件中將libcurl.so前面的;去掉,從新啓動apache便可。
第三項須要下載oauth-php代碼到本地。apache

開始:

因爲本文在本地localhost實現,裏面涉及到了幾方的資源分享問題。我暫時放在兩個目錄:

1 oauthdemo目錄

2 oauthphp目錄

本文中須要在數據庫中存儲用戶信息,下面須要創建一些基本數據庫了:

1 SQL,建立數據庫photo,並創建表user和image,並填一些初始數據。

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 ;

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 ;

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'
);

2 須要爲OAuth-php創建基本的數據表信息。

在目錄(個人機器,其餘機器類推一下):

E:\xampp\htdocs\oauthphp\oauth-php\library\store\mysql

裏面有install.php文件
去掉鏈接mysql的註釋部分,代碼以下了:

<?php

/**
 * Installs all tables in the mysql.sql file, using the default mysql connection
 */

/* Change and uncomment this when you need to: */


mysql_connect('localhost', 'root');
if (mysql_errno())
{
    die(' Error '.mysql_errno().': '.mysql_error());
}
mysql_select_db('oauthdemo');


$sql = file_get_contents(dirname(__FILE__) . '/mysql.sql');
$ps  = explode('#--SPLIT--', $sql);

foreach ($ps as $p)
{
    $p = preg_replace('/^\s*#.*$/m', '', $p);
   
    mysql_query($p);
    if (mysql_errno())
    {
        die(' Error '.mysql_errno().': '.mysql_error());
    }
}

?>

而後在瀏覽器中運行:

http://localhost/oauthphp/oauth-php/library/store/mysql/install.php

就完成了數據庫的初始化。

而後按照Fishing兄的博客,完成相應的php文件。這裏轉過來,方便你們閱讀:
在本文的oauthphp目錄:

建立config.inc.php文件:

<?php
// 數據庫鏈接信息
$dbOptions = array(
    'server'   => 'localhost',
    'username' => 'root',
    'password' => '',
    'database' => 'photo'
);
?>

建立oauth_register.php:

 

<?php
// 當前登陸用戶
$user_id = 1;
 
// 來自用戶表單
$consumer = array(
    // 下面兩項必填
    'requester_name'         => 'Fising',
    'requester_email'        => 'Fising@qq.com',
 
    // 如下均爲可選
    '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;
?>

在瀏覽器執行本php文件:

http://localhost/oauthphp/oauth-php/oauth_register.php

至關於自動註冊一個應用(其實就是一個消費方Client)。而且將該應用的 App Key 和 App Secret呈現給你。下面 www.demo.com 站點將使用這2個字符串進行認證。因此如今先把這兩個值保存起來備用。

便可獲得:

Your App Key: 07be533fdd42a946e214a1a487b8943704f4c9501
Your App Secret: fb4c7f6a36f7941ce311d63dbf46c383

這樣,消費方註冊功能就完成了。

接下來,消費方 oauthdemo 就可使用這個 App Key 和 App Secret,向認證服務器請求未受權的 Request token 了。這一步須要作兩件事情:① 消費方 oauthdemo 向 OAuth Server 也就是 oauthphp 請求未受權的 Request token;② OAuth Server 處理消費方的請求,生成並將未受權的 Request token 返回給消費方;

再創建文件request_token.php

 

<?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」了。

接下來,咱們須要配置另一個服務器了,就是demo服務器了。

建立oauthdemo數據庫,使用

http://localhost/oauthdemo/oauth-php/library/store/mysql/install.php

來創建基本數據庫表,不過其中的鏈接數據庫須要修改成oauthdemo數據庫:
文件爲:

<?php

/**
 * Installs all tables in the mysql.sql file, using the default mysql connection
 */

/* Change and uncomment this when you need to: */


mysql_connect('localhost', 'root');
if (mysql_errno())
{
    die(' Error '.mysql_errno().': '.mysql_error());
}
mysql_select_db('oauthdemo');


$sql = file_get_contents(dirname(__FILE__) . '/mysql.sql');
$ps  = explode('#--SPLIT--', $sql);

foreach ($ps as $p)
{
    $p = preg_replace('/^\s*#.*$/m', '', $p);
   
    mysql_query($p);
    if (mysql_errno())
    {
        die(' Error '.mysql_errno().': '.mysql_error());
    }
}

?>

在目錄oauthdemo建立如下文件:
config.inc.php

<?php
// 數據庫鏈接信息
$dbOptions = array(
    'server'   => 'localhost',
    'username' => 'root',
    'password' => '',
    'database' => 'oauthdemo'
);
?>

而後,在消費方服務器根目錄繼續添加一個文件,add_server.php, 用來向消費方的數據庫中存儲認證服務器的信息。其實通常的,這個信息可能會直接寫在配置文件裏,不過,oauth-php提供了更增強大的數據庫的存儲方案而已。該文件的內容是:

<?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'      => '07be533fdd42a946e214a1a487b8943704f4c9501',
    'consumer_secret'   => 'fb4c7f6a36f7941ce311d63dbf46c383',
    'server_uri'        => 'http://localhost/oauthphp/',
    'signature_methods' => array('HMAC-SHA1', 'PLAINTEXT'),
    'request_token_uri' => 'http://localhost/oauthphp/request_token.php',
    'authorize_uri'     => 'http://localhost/oauthphp/authorize.php',
    'access_token_uri'  => 'http://localhost/oauthphp/access_token.php'
);
 
// 將服務器信息保存在 OAuthStore 中
$consumer_key = $store->updateServer($server, $user_id);
?>

這樣,經過瀏覽器訪問一下該文件,http://localhost/oauthdemo/add_server.php, 服務器的相關信息就會被保存起來了。用於生產環節時,這裏多是一個簡單的管理系統,能夠用來管理認證服務器列表。注意,上面文件裏的 key 和 secret 正是咱們以前在認證服務器 http://localhost/oauthphp 註冊消費方應用時獲得的。

有了認證服務器的相關信息,咱們如今能夠去獲取「未認證的token」了。在 http://localhost/oauthdemo/ 根目錄新建一個文件 index.php:

<?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 = '07be533fdd42a946e214a1a487b8943704f4c9501';
 
    // 從服務器獲取未受權的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
}
?>

這裏要注意,原文這裏有一個小問題,也許fishing兄疏忽了,就是
$consumer_key的設定不對,想留言的,不知爲什麼沒法註冊。

執行http://localhost/oauthdemo/index.php?req=1將返回:

array(2) { ["authorize_uri"]=> string(39) 「http://localhost/oauthphp/authorize.php」 ["token"]=> string(41) 「6959214bfc5c2a946052ea805292c7cc04f4f600f」 }

獲取「未受權的token」這一步,已經順利完成了。

接下來,根據 OAuth 驗證的流程,應該是重定向用戶瀏覽器到 http://localhost/oauthphp/ 進行 token 受權。

在 http://localhost/oauthdemo/ 服務器根目錄新建一個文件 authorize.php, 代碼以下:

<?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以進行驗證的頁面
    // ** 你的代碼 **
}
?>

附上代碼吧:
htdocs

參考文章:

1 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 2 http://www.fising.cn/?p=581 3 http://oauth.net/documentation/getting-started/ 4 http://lds2008.blogbus.com/tag/OAuth/ 5 http://iamcaihuafeng.blog.sohu.com/154447409.html 6 http://www.cnblogs.com/youxilua/archive/2011/12/29/2306790.html 7 http://www.fising.cn/2011/06/%E5%9F%BA%E4%BA%8Ephp%E7%9A%84oauth%E8%AE%A4%E8%AF%81%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E6%90%AD%E5%BB%BA.shtml

相關文章
相關標籤/搜索