【引用】基於 PHP & MySQL 搭建OAuth Server

接上一篇《一步一步搭建 OAuth 認證服務器》的文章,其實也就是介紹了一下 OAuth 的理解和 oauth-php 這個開源的項目,並沒有做出一個演示。今天這篇文章就來做一個Demo,我們基於 PHP 來搭建一個 OAuth認證服務器。開始吧!

爲了方便理解,可以先看一下在 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',
'requester_email' => '[email protected]',
// 以下均爲可選
'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以進行驗證的頁面
// ** 你的代碼 **
}
?>

如果用戶未登錄,則要求先行登陸才能進行授權操作。

發佈了120 篇原創文章 · 獲贊 8 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章