plai2010 / php-oauth2
PHP OAuth2 工具包。
Requires
- league/oauth2-client: ^2.7
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
。