decomplexity / sendoauth2
PHPMailer SMTP包装器
Requires
- php: >=7.4.0
- google/apiclient: ^2.15.0
- league/oauth2-google: >=4.0.1
- phpmailer/phpmailer: >=6.6.0
- thenetworg/oauth2-azure: >=2.2.1
README
PHPMailer SMTP包装器
注意:为了避免破坏性更改,V3(所有子版本)的grantTypeValue默认为authorization_code。在V4.0.0及以后版本中,参数grantTypeValue已更改为grantType。grantTypeValue仍然会被接受,但会被忽略,并应用默认值。这只会影响使用MSFT支持并带有client_credentials的用例。
SendOauth2 V4.1.0支持OAuth2和Basic身份验证,适用于Microsoft 365 Exchange电子邮件和Google Gmail。Yahoo(以及因此AOL)支持通过authorization_code授权流程获得的Oauth2访问令牌(它们不支持client_credentials授权)来使用其SMTP网关。包装器不支持Yahoo / AOL,也没有计划支持。Amazon SES SMTP有自己的凭证管理系统,包装器不支持它。
Microsoft支持使用Graph V1和V2身份验证和授权端点的Microsoft 365账户。Google支持任何非旧版Gmail。
当使用“完整”包装器(如下面的选项c所示)时,SendOauth2会自动更新Microsoft 365电子邮件的刷新令牌;对于Gmail来说,通常是不必要的,因为Google刷新令牌默认不会过期。
Microsoft支持客户端密钥和X.509证书。TheLeague的Gmail提供者仅支持客户端密钥(仅限于authorization_code授权)。
Google的Gmail API支持authorization_code授权的客户端密钥和client_credentials(即Google“服务账户”)的X.509证书授权,包装器都支持这两种方式。Google使用.json凭证文件与PHPMailer已建立的客户端身份验证机制不一致,因此包装器会自动创建这些文件,尽管可以使用Google自己的文件(作为可选)。V4.1.0引入了两个新的可选参数,用于调用标准PHPMailer电子邮件应用程序(如下面的a和b所示),以及完全替换您的PHPMailer应用程序的包装器前端(如下面的c所示)。这些参数允许开发人员指定.json凭证文件名称以及包装器是否应动态构建此.json文件或使用已构建的一个,该文件可以是Google(标准下载)或开发人员创建的。
对于类似PHPMailer的守护程序应用程序,客户端凭证授权(即用户授权)比authorization_code授权更合适。对于Microsoft,SMTP支持authorization_code授权和客户端凭证(即应用程序)授权流程。
对于Google API,SMTP支持authorization_code授权和客户端凭证(服务账户)授权(请参阅github存储库/library googleapis/google-api-php-client),TheLeague的Google Gmail提供者仅支持authorization_code授权,这由包装器支持。
如果提供程序或客户端支持,包装器将自动实现$_SESSION 'state'交换和PKCE代码交换,以防止在authorization_code流程中发生CSRF攻击。
使用包装器有三种非常不同的方式
a.在一个其他“标准”PHPMailer电子邮件应用程序中,用SendOauth2B的实例替换提供者,例如oauth2-azure,以及PHPMailer的OAuth2。调用SendOauth2B需要所有常规OAuth2参数,例如clientID。由电子邮件服务提供商提供的特定参数(如适当的scope参数)将自动提供(由SendOauth2C提供)。PHPMailer的示例文件夹有一个完整的示例应用程序。
b. 类似于a.,但不需要向SendOauth2B提供众多的OAuth2参数(多达16个),只需传递PHPMailer对象(在您的代码中实例化)和所选的'认证集'的名称或编号。后者如下所述,是创建用于离线生成初始刷新令牌的潜在多个认证参数组之一。新的刷新令牌和其他参数随后存储在一个记录文件中,供SendOauth2B以后使用。
c. 完全用前端的SendOauth2A替换您的PHPMailer应用程序 - SendOauth2A,除了其他功能外,还会刷新Microsoft刷新令牌,并将其写回到记录文件中,以便在下次调用PHPMailer时使用。
本文件的其余部分基本上是针对b.和c.的;a.在PHPMailer仓库中的示例应用程序中已有说明。
为什么需要包装? 非平凡的网站通常在许多地方使用电子邮件(联系页面、购买确认、PayPal IPN等),在每一页中包含PHPMailer调用代码和邮件设置会使维护变得繁重,尤其是如果OAuth2被设置为在每个点使用不同的客户端密钥 - 这是一种更安全的方法。此外,Microsoft刷新令牌的有效期最长为90天,在此期间,发行者必须重新授权以获取新的令牌,除非在此期间他或她已授权延长现有令牌的有效期('90天'是最大不活跃时间)。另一种选择是在每次颁发访问令牌时请求新的刷新令牌。
如c.以上所述,使用完整的SendOauth2包装,一个页面可以包含尽可能少的
new SendOauth2A ($mailStatus,[ 'mailTo' => ['john.doe@deer.com'], 'mailSubject' => 'Deer dear!', 'mailText'=>'Lovely photo you sent. Tnx', 'mailAuthSet' => '1' ]);
以及一些额外的'admin' PHP行。
SendOauth2的目的是简化OAuth2认证和授权的实施,这对于Microsoft来说,比基本认证复杂得多,尽管SendOauth2也支持基本认证,以便更容易过渡到OAuth2。
1. 安装
使用Composer获取SendOauth2、PHPMailer、thenetworg的Microsoft提供者、TheLeague的oauth2-google提供者、Google API(它既是提供者也是服务)等最新稳定版本。Composer会为您做所有这些。
Composer会将SendOauth2、PHPMailer和提供者安装到您的网站vendor/decomplexity/sendoauth2/src文件夹中;只需在您的json中指定
{
"require": {
"decomplexity/SendOauth2": ">=4.1"
}
}
当Composer'需要'Google API的Gmail支持时,它将安装到apiclient-services文件夹中,而不仅仅是Gmail,还包括所有Google服务。除了这会浪费空间外,它包含的大量文件如果在桌面客户端等处下载可能会成为问题。您可以从apiclient-services/src手动删除除Gmail.php和Gmail文件夹之外的所有文件(但似乎这个文件夹也不需要),或者在使用以下Composer脚本之前使用
{
"require": {
"google/apiclient": "^2.15.0"
},
"scripts": {
"pre-autoload-dump": "Google\\Task\\Composer::cleanup"
},
"extra": {
"google/apiclient-services": [
"Gmail"
]
},
}
在运行此脚本之前,您可能需要删除apiclient-services文件夹,然后运行Composer update。
目前(2024年3月)需要两个代码更改
**NB(1):当前需要对thenetworg oauth2-azure Azure.php的Microsoft提供者进行一个代码更改,这不能作为一个覆盖来执行
- 对于版本19(v2.2.2),在第40行和280行,将graph.windows.net替换为graph.microsoft.com ** Azure的任何后续版本可能已经对此进行了修正,因为Azure AD Graph已被Microsoft弃用并由Microsoft Graph取代。
**NB(2): 如果您希望自动更新刷新令牌,需要在 TheLeague oauth2-client/src/Token/AccessToken.php 中添加一行代码。包装器会检查这个更改;如果不存在,则包装器将默认让刷新令牌以正常方式过期(如果它们会过期;Google 的通常不会)。
if (!empty($options['refresh_token'])) {
$this->refreshToken = $options['refresh_token'];
在 } 之前添加以下行:
$_SESSION[__NAMESPACE__ . "\\updatedRefreshToken"] = $this->refreshToken;
2. 提供者
Microsoft 提供者是 networg * oauth2-azure *,由 Jan Hajek 等人编写。Google API 是由 Google 编写的“官方”客户端 API。Gmail 提供者是 PHP League * oauth2-google *,由 Woody Gilk 等人编写。
3. 类和文件
SendOauth2 包含四个 PHP 类和一个 Traits,这些类和 Traits 存储在默认的 vendor/decomplexity/sendoauth2/src 文件夹中对应的 PHP 文件中。
还有三个文件分布在 Examples 文件夹中,应将其移动到 /vendor 的父文件夹中,以便开发者进行修改。其中一个文件(SendOauth2D-settings)是用于认证最多五个电子邮件服务的模板:Microsoft 365 OAuth2、Microsoft 365 基本身份验证(用户 ID 和密码)、TheLeague 的 Google Gmail OAuth2 和基本身份验证以及 Google API OAuth2(使用服务账户时具有“域范围委派”)。此文件采用 PHP 'switch' 块的形式,具有五个 'cases',并且由 SendOauth2D 类所必需。其他两个文件(SendOauth2A-invoke 和 SendOauth2D-invoke)是用于实例化 SendOauth2A(用于发送邮件)和 SendOauthD(用于授权代码授予,获取 OAuth2 刷新令牌)的模板。SendOauth2A-invoke 中的示例代码旨在编辑并集成到开发者的网站页面上,当使用完整的包装器时;如果开发者选择通过其他“标准”PHPMailer 电子邮件应用程序使用包装器,这也得到支持,并在 PHPMailer 'Examples' 中给出了示例。
流程摘要(使用完整包装器时)
Microsoft 和 Google OAauth2 设置 => 粘贴 => SendOauth2D-settings
调用 SendOauth2D <=> SendOauth2C(提供者工厂)
SendOauth2D => 编写包含刷新令牌等信息的交换文件
调用 SendOauth2A => SendOauth2B 读取交换文件
SendOauth2B 进行身份验证,然后 => SendOauth2A 进行 PHPMailer 发送
THE CLASSES
- SendOauth2A - 从全局 PHP 实例化(请参阅后面的示例)
- SendOauth2B - 从 SendOauthA 实例化,主要用于执行 OAuth2 身份验证
- SendOauth2C - 提供者工厂类。它由 SendOauth2B 和 SendOauth2D 实例化
- SendOauth2D - 可以独立地从全局 PHP 几行代码中实例化,如以下第 7 节中所示。
- SendOauth2ETrait - 由 SendOauth2B 和 SendOauth2D 用于创建和写入 Google API 所使用的 .json 凭证文件。
SendOauth2D '需要' 一个名为 SendOauth2D-settings.php 的文件,该文件包含安全设置,如 clientId、clientSecret、redirectURI、服务提供商(Microsoft、Google)、认证类型(例如 XOAUTH2)等。对于每个需要不同安全设置或不同提供商的 PHPMailer 调用(通常是单个网站页面),都有一组这些安全设置。一个网站可以使用 Microsoft 和 Google(以及后来添加到 SendOauth2C 的其他任何服务)的任何组合,以及任意数量的不同安全组。每个设置都有一个唯一的标识符(在模板 SendOauth2D-settings 中编号为 1、2、3、4),但开发者可以使用更有意义的内容。当 SendOauth2D 通过 SendOauth2D-invoke 实例化时(见下文第 7 节),后者指定组号,然后 SendOauth2D 生成一个包含 OAuth2 的刷新令牌以及其他安全设置(如客户端 ID 和客户端密钥)的“交换”文件。如果选择基本认证组,则文件输出类似,但包括 SMTP 密码,不包括 Oauth2 设置。每个安全设置组都有一个交换文件。
例如,当 SendOauth2A 从网页实例化时,相关的组号会传递给它。然后,这个组号会传递给 SendOauth2B,它读取适当的安全设置交换文件。
因此,ClientId、clientSecret、redirectURI 和 refreshToken 只需从 Microsoft AAD 或 Google console.cloud 复制到 SendOauth2D-settings。无需将其复制到调用 PHPMailer 的代码中,因为它们已“在文件”中可用。这也意味着,如果需要,SendOauth2D-settings 可以随后移动到更安全的地方,并且在 SendOauth2D 和 SendOauth2B 中分别有虚拟加密和解密指示器,以便开发者在交换文件中添加额外的安全性。
4. 服务设置
对于 Microsoft AAD 客户端设置,只要 SendOauth2D 使用用户主体名称(电子邮件地址)进行登录认证,似乎没有必要添加 'offline_access' 和 'SMTP.Send' Graph 权限,因为 Graph 会自动添加它们。这是由于 Microsoft 将 Exchange(outlook.office.com)实现为 OAuth2 认证的 SMTP 发送的资源 API,而不是 Graph(尽管 Exchange 本身现在没有 SMTP.Send 权限可使用!)。如果 SendOauth2D 使用同一租户中的另一个电子邮件账户的登录进行认证,则可能需要将这些作为 Graph 权限添加,并为租户“授予管理员同意”。MSFT 范围权限很奇特;它们在 PHPMailer 的 WiKi 文档“Microsoft OAuth2 SMTP 问题”中有详细解释。要使用 Microsoft client_credentials 授权,应用程序必须注册为服务主体(使用 Exchange Online PowerShell),因为调用 Exchange 资源的不是用户(无论是用户主体还是委托用户) - 而是应用程序本身;请参阅https://learn.microsoft.com/en-gb/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#authenticate-connection-requests。
Google 不那么奇特,但确保在通过 OAuth 同意屏幕添加权限时,已启用 Gmail API(否则您将无法找到 'mail.google.com' 以选择它!)!当使用 GoogleAPI 中的客户端凭据(服务帐户)时,访问 Gmail 的权限设置在: https://console.cloud.google.com/apis/credentials 和 https://console.cloud.google.com/iam-admin/serviceaccounts(高级设置 / 域名范围委托)=> https://admin.google.com/ => 安全 => 访问和数据控制 => API 控制 => 管理域名范围委托 => 添加新的 [API 客户端]。注册模拟服务帐户的注册类似于注册亚马逊网络服务的安全令牌服务 API '角色'。
5. SendOauth2D 设置
为 SendOauth2D-settings 定义安全设置
- 选择合适的样本安全组(Microsoft XOAUTH2 是第一个也是默认选项)并将从 Microsoft AAD 或 Google console.cloud 复制的设置插入。还有两个附加设置
- 'SMTPAddressDefault' - 将其设置为用户主体名称(例如,AAD 管理员电子邮件地址)
- 'fromNameDefault' - 您想要用于电子邮件 * From * 名称的默认设置。这两个设置在调用 SendOauth2A 时都可以被覆盖。
6. SendOauth2D 实例化
您用于实例化 SendOauth2D 的代码 必须 与您指定给 Microsoft AAD 或 Google console.cloud 的重定向 URI 完全相同。SendOauth2D-invoke.php 是这样一个文件,但请记住,SendOauth2D-invoke 必须位于 /vendor 文件夹的父目录中。
所以重定向 URI 看起来像这样: https://mydomain.com/php/SendOauth2D-invoke.php
要选择安全组 1,它只需要包含
namespace decomplexity\SendOauth2; require 'vendor/autoload.php'; new SendOauth2D ('1');
7. SendOauth2A 实例化
SendOauth2A has two arguments: $mailStatus $options - an array of the options described below So instantiation looks something like: new SendOauth2A ($mailStatus,$options) It is preceded by: namespace decomplexity\SendOauth2; require 'vendor/autoload.php'; and followed by clearing the session variables and then your test for success or failure ($mailStatus returns "OK" for a successful send): $_SESSION = array(); if ($mailStatus == "OK") { echo ("Email sent OK"); } else { echo ("Sending failed. Error message: " . $mailStatus); }
SendOauth2A 选项
- 'mailTo'
- 'mailCC'
- 'mailBCC'
- 'mailFrom'
- 'mailReplyTo'
这些都是具有明显意义的数组。每个数组参数可以包含一个或两个值。第一个是一个电子邮件地址。可选的第二项是电子邮件地址的名称前缀。如果有两个值,它们是逗号分隔的。每个数组可以包含任意数量的参数(电子邮件收件人)。如果有多个,它们是逗号分隔的。在电子邮件地址中包含逗号的情况下,请确保它用引号转义。
- 'mailAttach'
- 'mailAttachInline'
- 'mailAttachString'
- 'mailAttachStringInline' 也是一个数组。它们允许作为文件或位字符串在 PHP 中可用的附件。不支持通过指定 URI 来附加文件 - 它必须首先上传或复制。
' mailAttach' 是简单的电子邮件附件,它接受单值参数,即文件名。
' mailAttachInline' 与之类似,但将文件(通常是图像)嵌入到 'mailText' 电子邮件正文中(见下文)。每个 'mailAttachInline' 数组参数有两个逗号分隔的值:文件名后跟任意文本字符串('cid')。然后电子邮件正文还必须包含一个 HTML 指示器,说明在哪里嵌入文件:这是以下形式:
' mailAttachString' 和 'mailAttachStringInline' 与 'mailAttach' 和 'mailAttachInline' 类似,但它们不是通过附件或嵌入文件,而是通过位字符串来这样做,这是 mailAttachString 参数的唯一值,以及 mailAttachStringInline 参数的两个逗号分隔值中的第一个。
-
'mailText' 字符串是电子邮件的 HTML 文本。
-
'mailTextPlain' 是可选的纯文本版本。如果没有指定 'mailTextPlain',则通过从 'mailText' 中删除 HTML 字符创建纯文本版本。
-
'mailSMTPAddress' 字符串覆盖了 SendOauth2D 中设置的默认 SMTP 'username'(一个地址)。这只有在您希望覆盖 'SMTPAddressDefault' 时才应在全局设置中设置。当 SendOauth2D 运行时,对于 Microsoft 至少,当前登录的租户成员被授权或请求登录以授权。这通常必须有一个与 'SMTPAddressDefault' 中设置的相同的电子邮件地址,否则 SendOAuth2B 身份验证失败。如果用于基本身份验证,除非 SMTP 密码与 SendOauthD 中的切换案例相同,否则将失败。为了提高安全性,全局不能覆盖后者,即全局没有 $mailSMTPPassword 操作数可以使用。使用的地址通过中间文件携带到刷新令牌中。
-
'mailAuthSet' 字符串是 SendOauth2D 组设置要使用(SendOauth2D 框架中的 1、2、3 或 4)。
操作数可以以任何顺序指定。
强制操作数
- 至少需要有一个'mailTo'、'mailCC'或'mailBCC'参数至少有一个参数(即至少需要一个电子邮件收件人!)
- 字符串'mailAuthSet'(你可以 * 允许 * 它默认为SendOauth2D中默认的switch case设置,但这不建议这样做)
- 参数字符串 $mailStatus
示例:为了清晰和简洁,下面的示例将显示参数值内联,而在实际应用中,将使用一个如 $options 的数组,其变量也将是PHP变量。换句话说
$options = [$destination,$CCto];
new SendOauth2A ($mailStatus,$options);
简单示例
namespace decomplexity\SendOauth2; session_start(); require 'vendor/autoload.php'; new SendOauth2A ($mailStatus,[ 'mailTo' => ['john.doe@deer.com'], 'mailSubject' => 'Deer dear!', 'mailText'=>'Lovely photo you sent. Tnx', 'mailAuthSet' => '1' ]); $_SESSION = array(); if ($mailStatus == "OK") { echo ("Email sent OK"); } else { echo ("Sending failed. Error message: " . $mailStatus); }
请注意,当指定PHP变量作为数组参数时,只有当它被双引号包围时才会解析。例如,当参数是一个字符串时:'mailFrom' => [“$fromsomeaddress, $fromsomename”],
更全面的示例
namespace decomplexity\SendOauth2; session_start(); require 'vendor/autoload.php'; new SendOauth2A ($mailStatus,[ 'mailTo' => ['john.doe@deer.com, John Doe', 'jaime.matador@gmail.com,Jaime Cordobes'], 'mailCC' => ['jane.roe@gmail.com, Jane Doe', 'june.buck@outlook.com'], 'mailBCC' => ['jack.foe@battleme.co.uk, Jack the Lad'], 'mailFrom' => ['hugo.cholmondeley@veryposh.com, Hugo Plantagenet'], 'mailReplyTo' =>['lucinda@veryposh.com, Lucinda Leveson-Gower'], 'mailSubject' => 'Windsor Castle', 'mailText'=>'Re my knighthood - see <img src="cid:cholmondeley-pic"> for our coat of arms. A letter also attached', 'mailAttach' =>['letter.jpg'], 'mailAttachInline' =>['coatofarms.jpg, cholmondeley-pic'], 'mailAuthSet' => '1' ]); $_SESSION = array(); if ($mailStatus == "OK") { echo ("Email sent OK"); } else { echo ("Sending failed. Error message: ". $mailStatus); }