使⽤OAuth2-Server-php搭建OAuth2Server
Yii 有很多 extension 可以使⽤,在查看了 Yii 官⽹上提供的与 OAuth 相关的扩展后,发现了⼏个 OAuth2 的客户端扩展,但是并没有到可以作为 OAuth2 Server 的扩展。因为 Yii 是组织良好的易于扩展的框架,所以完全可以集成其它的 PHP OAuth2 Server 实现⽅案。在OAuth/2/ 官⽹上,提供了⼏个 PHP 实现的 OAuth2 Server。这⾥使⽤第⼀个 OAuth2-Server-php 来作为 Yii 框架的 OAuth2 Server 扩展,需要进⾏⼀些必要的整合操作,主要是编写⼀个类来接受 client 访问和颁发 access_token 等。
第⼀部分:数据库准备
OAuth2-Server-php  使⽤的数据库结构采⽤ Github 上的 oauth2-server-php README.md 提供的表结构(Schema),⼀共有五张表:
mysql> show tables;
+--------------------------+
| Tables_in_oauth2        |
+--------------------------+
| oauth_access_token      |
| oauth_authorization_code |
| oauth_client            |
| oauth_refresh_token      |
| user                    |
+--------------------------+
5 rows in set (0.00 sec)
各表的名字说明了表中存取的内容,表名可⾃定义,⾃定义位置为:OAuth2/Storage/Pdo.php 48⾏的 config 数组中,因为这⾥采⽤的是mysql 数据库,所以需要修改的是 Pdo,若是采⽤其它的存储⽅案,如 Redis,则⾃⾏修改对应⽂件即可。注意这⾥的数据库名称是都是单数形式。
使⽤以下 sql 语句创建这5个表,并添加⼀个测试 client:
>>>>>>#
### oauth2 tables
>>>>>>#
drop table if exists `oauth_client`;
drop table if exists `oauth_access_token`;
drop table if exists `oauth_authorization_code`;
drop table if exists `oauth_refresh_token`;
drop table if exists `user`;
CREATE TABLE `oauth_client` (
`client_id` VARCHAR(80) NOT NULL,
`client_secret` VARCHAR(80) NOT NULL,
`redirect_uri` VARCHAR(2000) NOT NULL,
CONSTRAINT client_id_pk PRIMARY KEY (client_id)
);
CREATE TABLE `oauth_access_token` (
`access_token` VARCHAR(40) NOT NULL,
`client_id` VARCHAR(80) NOT NULL,
`user_id` VARCHAR(255),
`expires` TIMESTAMP NOT NULL,
`scope` VARCHAR(2000),
CONSTRAINT access_token_pk PRIMARY KEY (access_token)
);
CREATE TABLE `oauth_authorization_code` (
`authorization_code` VARCHAR(40) NOT NULL,
`client_id` VARCHAR(80) NOT NULL,
`user_id` VARCHAR(255),
`redirect_uri` VARCHAR(2000),
`expires` TIMESTAMP NOT NULL,
`scope` VARCHAR(2000),
CONSTRAINT auth_code_pk PRIMARY KEY (authorization_code)
);
CREATE TABLE `oauth_refresh_token` (
`refresh_token` VARCHAR(40) NOT NULL,
`client_id` VARCHAR(80) NOT NULL,
`user_id` VARCHAR(255),
`expires` TIMESTAMP NOT NULL,
`scope` VARCHAR(2000),
CONSTRAINT refresh_token_pk PRIMARY KEY (refresh_token)
);
--
CREATE TABLE `user` (
`user_id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(255) NOT NULL,
`password` VARCHAR(2000),
`first_name` VARCHAR(255),
`last_name` VARCHAR(255),
CONSTRAINT user_pk PRIMARY KEY (user_id)
);
-- test data
INSERT INTO oauth_client (client_id, client_secret, redirect_uri)
VALUES ("testclient", "testpass", "fake/");
INSERT INTO user (username, password, first_name, last_name)
VALUES ('rereadyou', '8551be07bab21f3933e8177538d411e43b78dbcc', 'bo', 'zhang');
第⼆部分:认证⽅案及实现
OAuth2 RFC 6749 规范提供了四种基本认证⽅案,以下针对这四种认证⽅案以及它们在本实现中的使⽤⽅式进⾏分别说⾯。
第⼀种认证⽅式: Authorization Code Grant (授权码认证)
授权码通过使⽤授权服务器做为客户端与资源所有者的中介⽽获得。客户端不是直接从资源所有者请求授权,⽽是引导资源所有者⾄授权服务器(由在RFC2616中定义的⽤户代理),授权服务器之后引导资源所有者带着授权码回到客户端。
在引导资源所有者携带授权码返回客户端前,授权服务器会鉴定资源所有者⾝份并获得其授权。由于资源所有者只与授权服务器进⾏⾝份验证,所以资源所有者的凭据不需要与客户端分享。
授权码提供了⼀些重要的安全益处,例如验证客户端⾝份的能⼒,以及向客户端直接的访问令牌的传输⽽⾮通过资源所有者的⽤户代理来传送它⽽潜在暴露给他⼈(包括资源所有者)。
授权码许可类型⽤于获得访问令牌和刷新令牌并未机密客户端进⾏了优化。由于这是⼀个基于重定向的流程,客户端必须能够与资源所有者的⽤户代理(通常是Web浏览器)进⾏交互并能够接收来⾃授权服务器的传⼊请求(通过重定向)。
Authorization Code Grant 过程(⼜称为 Web Server Flow) 参见如下:
+----------+
| Resource |
|  Owner  |
|          |
+----------+
^
|
(B)
+----|-----+          Client Identifier      +---------------+
|          +----(A)-- & Redirection URI ---->|              |
|  User-  |                                | Authorization |
|  Agent  +----(B)-- User authenticates --->|    Server    |
|          |                                |              |
|          +----(C)-- Authorization Code ---<|              |
+-|----|---+                                +---------------+
|    |                                        ^      v
(A)  (C)                                        |      |
|    |                                        |      |
^    v                                        |      |
+---------+                                      |      |
|        |>---(D)-- Authorization Code ---------'      |
|  Client |          & Redirection URI                  |
|        |                                            |
|        |<---(E)----- Access Token -------------------'
+---------+      (w/ Optional Refresh Token)
注:说明步骤(A)、(B)和(C)的直线因为通过⽤户代理⽽被分为两部分。
图1:授权码流程
在图1中所⽰的流程包括以下步骤:
(A)客户端通过向授权端点引导资源所有者的⽤户代理开始流程。客户端包括它的客户端标识、请求范围、本地状态和重定向URI,⼀旦访问被许可(或拒绝)授权服务器将传送⽤户代理回到该URI。
(B)授权服务器验证资源拥有者的⾝份(通过⽤户代理),并确定资源所有者是否授予或拒绝客户端的访问请求。
(C)假设资源所有者许可访问,授权服务器使⽤之前(在请求时或客户端注册时)提供的重定向URI重定向⽤户代理回到客户端。重定向URI包括授权码和之前客户端提供的任何本地状态。
(D)客户端通过包含上⼀步中收到的授权码从授权服务器的令牌端点请求访问令牌。当发起请求时,客户端与授权服务器进⾏⾝份验证。客户端包含⽤于获得授权码的重定向URI来⽤于验证。
(E)授权服务器对客户端进⾏⾝份验证,验证授权代码,并确保接收的重定向URI与在步骤(C)中⽤于重定向客户端的URI相匹配。如果通过,授权服务器响应返回访问令牌与可选的刷新令牌。
过程实现:
1.    client app 使⽤ app id 获取 authorization code:
www.yii/oauth2/index.php?r=oauth2/authroize&response_type=code&client_id=testclient&state=xyz
返回:$authcode = authorization code.
Tips:    authorization code will expired in 30s,可以修改 OAuth2/ResponseType/AuthorizationCode.php 中的 AuthorizationCode class 的构造⽅法配置参数来⾃定义 authorization_code 有效时间。
client_id 是之前注册在本 Server 上的应⽤名称,这属于客户端管理范畴。
这⼀步需要进⾏⽤户(资源所有者)登录 OAuth2 Server 来完成授权操作。⽤户登录属⽤户管理范畴,不属 OAuth2 Server 中应编写的功能。
⽤户登录后可选择⾃⼰可以向 client app 开放的操作(授权)。
这⼀步绑定过程中,从安全⾓度来考虑应强制⽤户重新输⼊⽤户名密码确认绑定,不要直接读取当前⽤户session进⾏绑定。
2. 获取 access_token:
client app 使⽤ authorization code 换取 access_token
curl -u testclient:testpass www.yii/oauth2/index.php?r=oauth2/token -d "grant_type=authorization_code&code=$authcode 返回:
成功:
{"access_token":"aea4a1059d3194a3dd5e4117bedd6e07ccc3f402",
"expires_in":3600,
"token_type":"bearer",
"scope":null,
"refresh_token":"269a623f54171e8598b1852eefcf115f4882b820"
}
失败:
{"error":"invalid_grant",
"error_description":"Authorization code doesn't exist or is invalid for the client"
}
Tip: 本步骤需要使⽤客户端的 client_id 和 client_secret 以及上⼀步获取的 authorization_code 换取 access_code.
access_tokne 有效期为 3600s, refresh_token 有效期为 1209600s,可以在 OAuth2/ResponseType/AccessToken.php 中的AccessToken class 中的构造函数配置中进⾏修改。
第⼆种认证⽅式: Implicit (隐式认证)
隐式授权类型被⽤于获取访问令牌(它不⽀持发⾏刷新令牌),并对知道操作具体重定向URI的公共客户端进⾏优化。这些客户端通常在浏览器中使⽤诸如JavaScript的脚本语⾔实现。
由于这是⼀个基于重定向的流程,客户端必须能够与资源所有者的⽤户代理(通常是Web浏览器)进⾏交互并能够接收来⾃授权服务器的传⼊请求(通过重定向)。
不同于客户端分别请求授权和访问令牌的授权码许可类型,客户端收到访问令牌作为授权请求的结果。
隐式许可类型不包含客户端⾝份验证⽽依赖于资源所有者在场和重定向URI的注册。因为访问令牌被编码到重定向URI中,它可能会暴露给资源所有者和其他驻留在相同设备上的应⽤。
采⽤Implicit Grant⽅式获取Access Token的授权验证流程⼜被称为User-Agent Flow,适⽤于所有⽆Server端配合的应⽤(由于应⽤往往位于⼀个User Agent⾥,如浏览器⾥⾯,因此这类应⽤在某些平台下⼜被称为Client-Side Application),如⼿机/桌⾯客户端程序、浏览器插件等,以及基于JavaScript等脚本客户端脚本语⾔实现的应⽤,他们的⼀个共同特点是,应⽤⽆法妥善保管其应⽤密钥(App Secret
Key),如果采取Authorization Code模式,则会存在泄漏其应⽤密钥的可能性。其流程⽰意图如下:
+----------+
| Resource |
|  Owner  |
|          |
+----------+
^
|
(B)
+----|-----+          Client Identifier    +---------------+
|          +----(A)-- & Redirection URI --->|              |
|  User-  |                                | Authorization |
|  Agent  |----(B)-- User authenticates -->|    Server    |
|          |                                |              |
|          |<---(C)--- Redirection URI ----<|              |
|          |          with Access Token    +---------------+
|          |            in Fragment
|          |                                +---------------+
|          |----(D)--- Redirection URI ---->|  Web-Hosted  |
|          |          without Fragment      |    Client    |
|          |                                |    Resource  |
|    (F)  |<---(E)------- Script ---------<|              |
|          |                                +---------------+
+-|--------+
|    |
(A)  (G) Access Token
|    |
^    v
+---------+
|        |
|  Client |
|        |
+---------+
注:说明步骤(A)和(B)的直线因为通过⽤户代理⽽被分为两部分。
图2:隐式许可流程
图2中的所⽰流程包含以下步骤:
(A)客户端通过向授权端点引导资源所有者的⽤户代理开始流程。客户端包括它的客户端标识、请求范围、本地状态和重定向URI,⼀旦访问被许可(或拒绝)授权服务器将传送⽤户代理回到该URI。
(B)授权服务器验证资源拥有者的⾝份(通过⽤户代理),并确定资源所有者是否授予或拒绝客户端的访问请求。
(C)假设资源所有者许可访问,授权服务器使⽤之前(在请求时或客户端注册时)提供的重定向URI重定向⽤户代理回到客户端。重定向URI在URI⽚段中包含访问令牌。
(D)⽤户代理顺着重定向指⽰向Web托管的客户端资源发起请求(按RFC2616该请求不包含⽚段)。⽤户代理在本地保留⽚段信息。(E)Web托管的客户端资源返回⼀个⽹页(通常是带有嵌⼊式脚本的HTML⽂档),该⽹页能够访问包含⽤户代理保留的⽚段的完整重定向URI并提取包含在⽚段中的访问令牌(和其他参数)。
(F)⽤户代理在本地执⾏Web托管的客户端资源提供的提取访问令牌的脚本。
(G)⽤户代理传送访问令牌给客户端。
Tips: 1. ⼀般不需提供 client_secret,仅需 client_id,单⽤户同样需要认证。
2. Implicit Grant Type 不⽀持 refresh_token(或可⾃⾏实现)机制。
3. THE FIRST TIME THE USER AUTHENTICATES YOUR APP USING IMPLICIT GRANT FLOW STORE THE ACCESS TOKEN! Once you have the access token do not try to re-authenticate. Your access token that you stored should continue to work!
⼀旦获取 access_token (存在于 redirect_uri 的 fragment 中,即 uri 中的 # 部分),Client 需要⾃⼰存储 access_token。
4. ⽐较适⽤于 Client-Side Application,如⼿机/桌⾯客户端程序、浏览器插件等
oauth2-server-php 对本授权⽅式的实现如下:
1. 这种授权⽅式包含于 Authorization Code Grant (是对 Authorization Code Grant ⽅式的简化)。
初始化 OAuth2Controller 时,只需向 OAuth2 Server 添加 AuthorizationCode 类型的授权即可,如下:
$server->addGrantType(new OAuth2\GrantType\AuthorizationCode($storage));
Authorization Code 默认不⽀持 Implicit Grant, 需要将 Server.php 第 104 ⾏的 'allow_implicit' 修改为 'true' 以开启 Implicit 授权。
2. 获取 access_token
www.yii/oauth2/index.php?
r=oauth2/authorize&response_type=token&client_id=testclient&state=xyz&redirect_uri=www.baidu
参数: response_type=token (必须,固定值)
client_id (必须)
redirect_uri 可选
scope    可选
state    推荐
注意:response_type = token ⽽不是 code, 因为隐式授权不⽤获取 authorization code。
返回:
成功:
需要⽤户先点击授权按钮。
SUCCESS! Authorization Code: www.baidu?#access_token=9f0c38b475e51ccd3
出错: redirect_uri 与注册的 client redirect_uri 不匹配。
{"error":"redirect_uri_mismatch","error_description":"The redirect URI provided is missing or does not
match","error_uri":"http:\/\/\/html\/rfc6749#section-3.1.2"}
access_token 存在于 redirect_uri 中的⽚段(fragment)中,即‘#’符号之后,client 需要⾃⼰提取⽚段中的 access_token 并注意保存。开发⼈员应注意,⼀些⽤户代理不⽀持在HTTP“Location”HTTP响应标头字段中包含⽚段组成部分。这些客户端需要使⽤除了3xx重定向响应以外的其他⽅法来重定向客户端——-例如,返回⼀个HTML页⾯,其中包含⼀个具有链接到重定向URI的动作的“继续”按钮。
第三种认证⽅式: Resource Owner Password Credentials (资源所有者密码凭证许可)
资源所有者密码凭据许可类型适合于资源所有者与客户端具有信任关系的情况,如设备操作系统或⾼级特权应⽤。当启⽤这种许可类型时授权服务器应该特别关照且只有当其他流程都不可⽤时才可以。
这种许可类型适合于能够获得资源所有者凭据(⽤户名和密码,通常使⽤交互的形式)的客户端。通过转换已存储的凭据⾄访问令牌,它也⽤于迁移现存的使⽤如HTTP基本或摘要⾝份验证的直接⾝份验证⽅案的客户端⾄OAuth。
+----------+
| Resource |
|  Owner  |
|          |
+----------+
v
|    Resource Owner
(A) Password Credentials
|
v
+---------+                                  +---------------+
|        |>--(B)---- Resource Owner ------->|              |
|        |        Password Credentials    | Authorization |
| Client  |                                  |    Server    |
|        |<--(C)---- Access Token ---------<|              |
|        |    (w/ Optional Refresh Token)  |              |
+---------+                                  +---------------+
图3:资源所有者密码凭据流程
图3中的所⽰流程包含以下步骤:
(A)资源所有者提供给客户端它的⽤户名和密码。
(B)通过包含从资源所有者处接收到的凭据,客户端从授权服务器的令牌端点请求访问令牌。当发起请求时,客户端与授权服务器进⾏⾝份验证。
(C)授权服务器对客户端进⾏⾝份验证,验证资源所有者的凭证,如果有效,颁发访问令牌。
Tips: 客户端⼀旦获得访问令牌必须丢弃凭据。
oauth2-server-php 对 Resource Owner Password Credentials 的实现如下:
php项目搭建1. ⾸先在 Oauth2Controller 的构造函数中添加对于 Resource Owner Password Credentials 授权⽅式的⽀持,加⼊以下代码:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。

发表评论