群里的损友们又开始叫了,说在等待我的关于 OAuth 服务器搭建 Demo 的介绍文章。这段时间一直很忙,人一忙,偶尔的一点闲暇就想睡觉,啥也不想做,学习上确实有些懈怠。此处悔过5分钟…….
我之前写过一篇《一步一步搭建 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'
=
>
'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
;
?>
|
这时候,通过浏览器访问: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以进行验证的页面
// ** 你的代码 **
}
?>
|
如果用户未登录,则要求先行登陆才能进行授权操作。