plai2010/php-oauth2

PHP OAuth2 工具包。

v1.3 2024-04-27 21:10 UTC

This package is not auto-updated.

Last update: 2024-09-28 23:36:15 UTC


README

这是一个用于处理PHP应用程序中OAuth2的实用程序包。它允许通过一个浏览器和一个并行的命令行shell来通过授权码授予获取OAuth2访问令牌;无需为OAuth2提供者设置工作Web端点以进行回调。

支持具有有效重定向的在线授权流程。有关如何在Laravel应用程序中设置,请参阅本节

此包的使用场景是在Laravel (10.x)应用程序中实现SMTP XOAUTH2身份验证。包含了一个Laravel服务提供者。

此包依赖于league/oauth2-client,特别是它的GenericProvider

安装

可以从Packagist安装此包

$ composer require plai2010/php-oauth2

也可以从Github克隆源代码仓库

$ git clone https://github.com/plai2010/php-oauth2.git
$ cd php-oauth2
$ composer install

示例:获取Outlook SMTP的访问令牌

假设一个Web应用程序已在Microsoft Azure AD中注册

* Application ID - 11111111-2222-3333-4444-567890abcdef
* Application secret - v8rstf8eVD5My89xDOTw8CoKG6rIw9dukIjHYzPU
* Redirect URI - https:///example

对于我们的目的,重定向URI不应指向实际的网站。它可以是指定格式的任何URL;只需通过浏览器进行测试,确保它产生404错误。

创建一个PHP脚本,例如outlook-oauth2.php

<?php
return [
	'provider' => [
		// As registered with the OAuth2 provider.
		'client_id' => '11111111-2222-3333-4444-567890abcdef',
		'client_secret' => 'v8rstf8eVD5My89xDOTw8CoKG6rIw9dukIjHYzPU',
		'redirect_uri' => 'https:///example',

		// These items are OAuth2 provider specific.
		// The values here are for Microsoft OAuth2.
		'scope_separator' => ' ',
		'url_access_token' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
		'url_authorize' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',

		// Other options.
		'pkce_method' => 'S256',
		'timeout' => 30,
	]
];

要获取SMTP登录(XOAUTH2)的OAuth2令牌,启动一个交互式PHP shell

$ php -a
Interactive shell

php >

获取授权URL

php > require_once 'vendor/autoload.php';
php > config = require('outlook-oauth2.php');
php > // The name 'outlook_smtp' does not matter in this example.
php > $oauth2 = new PL2010\OAuth2\OAuth2Provider('outlook_smtp', $config);
php > // Scope is OAuth2 provider specific.
php > // The value here is for Outlook SMTP login authorization by
php > // Microsoft OAuth2; offline_access to request refresh token.
php > $scope = [ 'https://outlook.office.com/SMTP.Send', 'offline_access' ];
php > $url = $oauth2->authorize('code', $scope);
php > echo $url, PHP_EOL;

将打印出类似这样的URL作为结果(行中断已插入)

https://login.microsoftonline.com/common/oauth2/v2.0/authorize
	?scope=...
	&state=...
	&response_type=code
	&client_id=...
	&...

不要结束交互式PHP shell。将URL复制到浏览器中,并完成授权步骤。最后,浏览器将被重定向到一个不存在的页面,URL与重定向URI匹配。回到交互式PHP shell处理该URL

php > // Get from browser the URL of the 'not found' page.
php > $redir = 'https:///example?code=...&state=...';
php > $token = $oauth2->receive($redir);
php > echo json_encode($token, JSON_PRETTY_PRINT+JSON_UNESCAPED_SLASHES);
{
	"token_type": "Bearer",
	"scope": "https://outlook.office.com/SMTP.Send",
	"ext_expires_in": 3600,
	"access_token": "EwBFB+l3BAK...",
	"refresh_token": "M.C732_B...",
	"expires": 1689702075
}
php > 

令牌可以保存到某些存储(例如数据库)中,供应用程序使用。如果令牌有到期时间且可刷新,则应用程序会请求刷新。如下所示

// Retrieve token from storage.
$token = [ ... ];

// Refresh token if it is expiring in say a minute.
$ttl = 60;
$oauth2 = new PL2010\OAuth2\OAuth2Provider(...);
$refreshed = $oauth2->refresh($token, $ttl);
if ($refreshed !== null) {
	// Save refreshed token to storage.
	...
	$token = $refreshed;
}

// Use $token ...

提供者管理器

一个应用程序可能与多个OAuth2提供者交互。例如,它可能需要Google的授权来访问Google Drive中的文件,以及Microsoft的授权通过Outlook SMTP发送电子邮件。OAuth2Manager使多个OAuth2提供者可用。例如,

php > // Create manager.
php > $manager = new PL2010\OAuth2\OAuth2Manager;
php > // Configure 'google' provider for 'drive' and 'openid' purposes.
php > $manager->configure('google', [
	'provider' => [
		'client_id' => ...,
		'client_secret' => ...,
		'url_access_token' => 'https://oauth2.googleapis.com/token',
		'url_authorize' => 'https://#/o/oauth2/auth',
	],
	'usage' => [
		'drive' => [
			'scopes' => [
				'https://www.googleapis.com/auth/drive.file',
				'https://www.googleapis.com/auth/drive.resource',
				...
			],
		],
		'signin' => [
			'scopes' => [
				'openid',
				'email',
				...
			],
		],
	],
]);
php > // Configure 'microsoft' provider for 'smtp'.
php > $manager->configure('microsoft', [
	'provider' => [
		'client_id' => ...,
		'client_secret' => ...,
		'url_access_token' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
		'url_authorize' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
	],
	'usage' => [
		'smtp' => [
			'scopes' => [
				'https://graph.microsoft.com/mail.send',
			],
		],
	]
]);

然后,要与Microsoft交互以获取SMTP登录的访问令牌,可以这样做

php > $oauth2 = $manager->get('microsoft', 'smtp');
php > // Like before ...
php > $scope = [ 'https://outlook.office.com/SMTP.Send', 'offline_access' ];
php > $url = $oauth2->authorize('code', $scope);
...

令牌存储库

应用程序负责管理OAuth2令牌的存储。此包包括TokenRepository和一些作为模型的实现。在这个模型中,令牌由两部分键标识:提供者:用途。这两部分对应于OAuth2Manager::get()(src/OAuth2Manager.php)的参数,因此可以存储存储库和所需的刷新令牌。键方案可以根据应用程序进行扩展。例如,为了允许每个用户的OAuth2令牌,提供者:用途:user_id可能就足够了。

AbstractTokenRepository是一个抽象实现。只需提供tokenLoad()tokenSave()方法。

Laravel集成

此软件包包含一个Laravel服务提供者,它以两种方式提供单例OAuth2Manager

* Abstract 'oauth2' in the application container, i.e. `app('oauth2')`.
* Facade alias 'OAuth2', i.e. `OAuth2::`.

配置文件是config/oauth2.php。它按名称返回提供者配置的关联数组,如下所示

<?php
return [
	'google' => [
		'provider' => [
			'client_id' => ...,
			'client_secret' => ...,
			...
		],
		'usage' => [
			'drive' => [
				...
			],
			'signin' => [
				...
			],
		],
	],

	'microsoft' => [
		'provider' => [
			'client_id' => ...,
			'client_secret' => ...,
			...
		],
		'usage' => [
			'smtp' => [
				...
			],
		]
	],
];

没有设置TokenRepository,但以下是一个示例,将单例DirectoryTokenRepository设置为'oauth2_tokens'

use PL2010\OAuth2\Repositories\DirectoryTokenRepository;

app()->singleton('oauth2_tokens', function() {
	return new DirectoryTokenRepository(
		storage_path('app/oauth2_tokens'),
		app('oauth2')
	);
});

示例:Laravel应用程序中的在线流程

假设一个位于example.com的Laravel应用程序正在使用此软件包。可以在routes/web.php中添加一个路由以启动授权代码授权流程

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

// Presumably there would a button on some page to do make a POST request,
// but for simplicity we just use `GET` in our example.
Route::get('/oauth2/authorize/{provider}/{usage?}', function(
	Request $request,
	string $provider,
	?string $usage=null
) {
	/**
	 * @var \PL2010\OAuth2\OAuth2Manager $mgr
	 * @var \PL2010\OAuth2\OAuth2Provider $oauth2
	 */
	$mgr = app('oauth2');
	$oauth2 = $mgr->get($provider, $usage);
	$redirect = route('oauth2.callback', [
		'provider' => $provider,
		'usage' => $usage,
	]);
	$url = $oauth2->authorize('code', '', $redirect, function($state, $data) {
		// Preserve state data in cache for a short while.
		Cache::put("oauth2:flow:state:{$state}", $data, now()->addMinutes(15));
	});
	return redirect($url);
})->middleware([
	// There would be middlewares appropriate for the use case.
	'can:configureOAuth2',
])->name('oauth2.authorize');

使用与提供者管理器示例中相同的方式,请求https://example.com/oauth2/authorize/microsoft/smtp将重定向到Microsoft的login.microsoftonline.com

假设存在一个名为oauth2.callback的路由来接收授权代码。以下是它的样子

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

// Handle redirect from OAuth2 authorization provider.
Route::get('/oauth2/callback/{provider}/{usage?}', function(
	Request $request,
	string $provider,
	?string $usage=null
) {
	/**
	 * @var \PL2010\OAuth2\OAuth2Manager $mgr
	 * @var \PL2010\OAuth2\OAuth2Provider $oauth2
	 * @var \PL2010\OAuth2\Contracts\TokenRepository $tkrepo
	 */
	// Expect authorization state in the request.
	$state = $request->get('state');
	if (!is_string($state))
		return redirect('/')->with('error', 'Missing OAuth2 state');

	// Retrieve preserved state data.
	$data = Cache::get("oauth2:flow:state:{$state}");
	if (!$data)
		return redirect('/')->with('error', 'Invalid OAuth2 state');

	// Obtain access token.
	$mgr = app('oauth2');
	$oauth2 = $mgr->get($provider, $usage);
	$token = $oauth2->receive($request->fullUrl(), preserved:$data);

	// Save access token.
	$key = $provider.($usage != ''
		? ":{$usage}"
		: ''
	);
	$tkrepo = app('oauth2_tokens');
	$tkrepo->putOAuth2Token($key, $token);

	return redirect('/')->with('success', 'OAuth2 access token saved');
})->middleware([
	// There would be middlewares appropriate for the use case.
	'can:configureOAuth2',
])->name('oauth2.callback');

在Microsoft方面,OAuth2应配置为允许重定向URI https://example.com/oauth2/callback/microsoft/smtp