cdyweb / oauth-client
PHP OAuth 2.0 客户端
Requires
- php: >=5.3
- ext-openssl: *
- cdyweb/http-adapter: *
- fkooman/oauth-common: ~0.5.0
Requires (Dev)
- phpunit/phpunit: 4.1.*
README
简介
本项目提供了一个OAuth 2.0 "授权码授权"客户端,如RFC 6749第4.1节所述。
客户端可以通过一个PHP API进行控制,该API用于从尝试访问受OAuth 2.0保护的资源服务器的应用程序中使用。
特性
以下特性得到支持:
- "授权码授权"配置文件
- 刷新令牌
许可协议
根据自由软件基金会发布的GNU Lesser General Public License许可,许可协议为版本3的许可证,或者(根据您的选择)任何更高版本。
https://gnu.ac.cn/licenses/lgpl.html
这大致意味着如果您编写了一些使用此客户端的PHP应用程序,您不需要将应用程序也发布在(L)GPL下。有关确切详情,请参阅许可协议。
应用程序集成
如果您想将此OAuth客户端集成到您的应用程序中,您需要回答一些问题
- 我将在哪里存储访问令牌?
- 我如何使我的应用程序中的一个端点URL可用,该URL可以用作来自授权服务器的回调的跳转URL?
除了这些,您还需要来自授权服务器的OAuth客户端凭据以及您想连接的服务的REST API文档。例如,您需要知道authorize_endpoint、token_endpoint、client_id和client_secret。
至于存储访问令牌,此库包括两个后端。一个用于在数据库中存储令牌(使用PHP PDO抽象层),另一个用于在用户会话中存储它们。第一个需要一些设置,第二个非常容易使用(无需配置),但不会允许客户端在没有会话数据的情况下在资源服务器上访问数据。更健壮的实现将使用PDO支持的存储。对于测试目的或非常简单的设置,会话实现最有意义。
以下部分将介绍您需要执行的步骤,以便使客户端正常工作。
示例
除此之外,在example目录中还有一个完整的示例。这包括执行令牌请求和请求数据的index.php,还包括一个callback.php来展示如何使用Callback API。此外,还包含了一个用于与Composer一起使用的composer.json。
Composer
为了轻松集成到您的应用程序中,建议使用Composer来安装依赖项。
fkooman/oauth-client
以下是一个简单的composer.json文件示例,您可以将其用于
{
"name": "fkooman/my-demo-oauth-app",
"require": {
"fkooman/oauth-client": "dev-master"
}
}
客户端配置
您可以根据以下示例创建一个客户端配置对象。如果需要,您可以从应用程序中的配置文件获取它。以下是一个通用的ClientConfig类的示例
$clientConfig = new ClientConfig(
array(
"authorize_endpoint" => "https:///oauth/php-oauth/authorize.php",
"client_id" => "foo",
"client_secret" => "foobar",
"token_endpoint" => "https:///oauth/php-oauth/token.php",
)
);
还有一个名为GoogleClientConfig的类,您可以使用Google的client_secrets.json文件格式使用它
// Google
$googleClientConfig = new GoogleClientConfig(
json_decode(file_get_contents("client_secrets.json"), true)
);
Google类还会设置一些用于处理某些规范违规的Google特定选项。处理规范违反服务的配置选项包括:
allow_null_expires_in如果OAuth 2.0 AS返回"expires_in": null,则此设置在null时删除expires_in字段,从而假定令牌无限期有效。将此设置设置为true以启用此配置选项,默认值为false。AS与此行为:SurveyMonkey。default_token_type如果OAuth 2.0 AS完全省略了token_type字段。这允许您设置token_type。例如,如果您知道AS返回的是此类型,则可以将它设置为bearer。AS与此行为:Salesforce。- 在OAuth 2.0 AS不支持令牌端点的Basic身份验证时,使用请求体中的
credentials_in_request_body。这将迫使客户端使用client_id和client_secretPOST体字段来指定凭据。此选项还将使允许client_id包含冒号(:)成为可能。具有此行为的AS:Google、SurveyMonkey、GitHub。 - 如果服务器返回的规范违反了空字符串作为
scope,则使用default_server_scope。这将覆盖范围值,允许您设置一个值。这仅会在服务器范围为空字符串时设置,而不是在所有情况下作为默认值!具有此行为的AS:Nationbuilder。 - 在服务器要求您在刷新令牌请求中提供redirect_uri参数时,使用
use_redirect_uri_on_refresh_token_request。具有此行为的AS:Nationbuilder。 - 当AS将范围响应发送为逗号分隔值而不是空格分隔值时,使用
use_comma_separated_scope。 - 当AS将范围发送为数组而不是空格分隔的字符串时,使用
use_array_scope。 - 某些AS将expires_in值返回为字符串而不是数值。设置此值将把字符串转换为数字。
初始化API
现在您可以初始化Api对象
$api = new Api("foo", $clientConfig, new SessionStorage(), new Guzzle3Client());
在此示例中,我们使用SessionStorage令牌存储后端。这用于在用户会话中保留获取的令牌。出于测试目的,这已足够,对于生产部署,您将想要使用PdoStorage后端,请参阅下面。
您还需要提供一个Guzzle实例,或者在这种情况下,使用Guzzle3Client,它是一个HTTP客户端,用于交换授权代码以获取访问令牌,或者使用刷新令牌以获取新的访问令牌。如果您的应用程序已经使用Guzzle 6,还有一个可用的Guzzle6Client。
请求令牌
为了请求令牌,您需要使用两种方法:Api::getAccessToken()和Api::getAuthorizeUri()。第一个用于检查是否已提供令牌,第二个用于获取一个URL,您必须从应用程序中重定向浏览器到该URL。下面的示例将展示如何使用这些方法。
在您可以调用这些方法之前,您需要创建一个Context对象,以指定您为哪个用户请求此访问令牌以及您想在授权服务器上请求什么范围。
$context = new Context("john.doe@example.org", array("read"));
这意味着您将请求一个与john.doe@example.org绑定的令牌,范围为read。您在此处指定的用户通常是您在您的应用程序中使用的用户标识符,该应用程序想要与OAuth 2.0受保护的资源集成。在您的服务中,用户可以是john.doe@example.org。此标识符与远程服务的用户身份无关,它只是用于记录访问令牌。如果您不想请求任何特定范围,则可以使用array()。
现在您可以查看是否已提供访问令牌
$accessToken = $api->getAccessToken($context);
如果此调用返回false,则表示没有为该用户和范围提供访问令牌,并且无法通过使用刷新令牌通过后端渠道获取。这意味着从未有过令牌或它已过期。令牌仍然可以被吊销,但我们现在看不到,我们将在稍后尝试使用它时发现这一点。
假设getAccessToken($context)调用返回false,即没有令牌,我们必须获取授权
if (false === $accessToken) {
/* no valid access token available, go to authorization server */
header("HTTP/1.1 302 Found");
header("Location: " . $api->getAuthorizeUri($context));
exit;
}
这是在没有使用任何框架的情况下最简单的方法。如果您的应用程序使用框架,您可能可以使用它来执行“正确的”重定向,而无需自己设置HTTP头。您应该使用这个方法!
之后,此脚本的流程结束,用户将被重定向到授权服务器。到达那里后,用户接受客户端请求,然后将被重定向回您在OAuth 2.0服务提供商处注册的重定向URL。您还需要在此回调位置放置一些代码,请参阅下面的下一节。
假设您已经有一个访问令牌,即:Api::getAccessToken()的响应不是false,您现在可以尝试获取资源。通常,您可以通过设置Authorization头来请求资源,如下所示:Authorization: Bearer access_token,其中access_token是Api::getAccessToken()的值。
如果请求失败并返回401,则表示令牌存在问题。它可能已过期或被撤销。您可以删除当前存储的访问令牌并再次尝试。
$api->deleteAccessToken($context);
$api->deleteRefreshToken($context);
处理回调
上述情况假设您已经有一个有效的访问令牌。如果没有,您将被重定向到授权服务器,您必须在那里接受对您数据的访问请求。假设一切顺利,您将被重定向回您在OAuth 2.0服务中注册的重定向URI。
Callback类与Api类非常相似。我们假设您在这里也创建ClientConfig对象,就像在Api的情况下一样。假设此文件的内容位于callback.php。
try {
$cb = new Callback("foo", $clientConfig, new SessionStorage(), new Guzzle3Client());
$cb->handleCallback($_GET);
header("HTTP/1.1 302 Found");
header("Location: http://www.example.org/index.php");
} catch (AuthorizeException $e) {
// this exception is thrown by Callback when the OAuth server returns a
// specific error message for the client, e.g.: the user did not authorize
// the request
echo sprintf("ERROR: %s, DESCRIPTION: %s", $e->getMessage(), $e->getDescription());
} catch (Exception $e) {
// other error, these should never occur in the normal flow
echo sprintf("ERROR: %s", $e->getMessage());
}
这里只需要这些。授权代码将被从回调URL中提取,并用于获取访问令牌。访问令牌将被存储在令牌存储中,这里使用SessionStorage,浏览器将被重定向回执行Api调用的页面,这里为index.php。
令牌存储
您可以将令牌存储在SessionStorage或PdoStorage中。第一个已经在上文演示过,无需进一步配置,它只需开箱即用。
$tokenStorage = new SessionStorage();
PDO后端需要您指定要使用的数据库。
$db = new PDO("sqlite:/path/to/db/client.sqlite");
$tokenStorage = new PdoStorage($db);
在这两种情况下,您都可以在构造函数中使用$tokenStorage,在之前我们直接放置new SessionStorage()的地方。有关如何指定其他数据库,请参阅PHP PDO文档。
请注意,如果您使用SQLite,请注意,写入文件的目录需要对Web服务器可写!
如果您想使用表前缀,请使用构造函数的第二个参数设置前缀,例如
$tokenStorage = new PdoStorage($db, "foo_");
要查看您需要导入到数据库中的数据库模式,您可以使用bin目录中的脚本,第一个参数是您要为您的表使用的表前缀
$ php bin/php-oauth-client-create-tables foo_
如果您指定没有任何参数,则假设没有前缀,并且显示没有前缀的将要创建的表。
日志记录
为了记录OAuth库对令牌端点的所有请求,您可以使用例如Monolog适配器。下面是如何做到这一点的示例。对于生产系统,您可能想与您自己的日志框架集成,设置适当的日志级别,例如仅记录错误。
所以,您可以用以下片段代替
new Guzzle3Client()
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Guzzle\Plugin\Log\LogPlugin;
use Guzzle\Log\MessageFormatter;
use Guzzle\Log\MonologLogAdapter;
/* create the log channel */
$log = new Logger('my-app');
$log->pushHandler(new StreamHandler(sprintf("%s/data/client.log", __DIR__), Logger::DEBUG));
$logPlugin = new LogPlugin(new MonologLogAdapter($log), MessageFormatter::DEBUG_FORMAT);
$httpClient = new Client();
$httpClient->addSubscriber($logPlugin);
$guzzle3Client = new Guzzle3Client($httpClient);
现在您可以将$guzzle3Client传递给Api和Callback类,请求和响应(包括其主体)将被记录。
测试
要运行测试,您可以使用PHPUnit。您可以通过以下方式运行测试
$ php /path/to/phpunit.phar tests
从目录中获取。确保在运行测试之前,首先运行php /path/to/composer.phar install。默认情况下,使用SQLite PDO驱动程序来运行数据库测试。如果您想使用其他数据库进行测试,请将phpunit.xml.dist文件复制到phpunit.xml,并修改配置。例如,要使用MySQL,请按照以下方式进行配置
<php>
<var name="DB_DSN" value="mysql:dbname=oauth;host=localhost" />
<var name="DB_USER" value="foo" />
<var name="DB_PASSWD" value="bar" />
</php>