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支持client_credentials授权的客户端密钥和client_credentials(即Google '服务账户')授权的X.509证书,包装器都支持。Google使用.json凭证文件的方式与PHPMailer的既定客户端认证机制不符,因此包装器会自动创建这些文件,尽管可以选择使用Google自己的文件。V4.1.0引入了两个新的可选操作数,用于调用标准PHPMailer电子邮件应用程序(以下a和b)以及完全用包装器前端替换您的PHPMailer应用程序(以下c)。这些操作数允许开发人员指定.json凭证文件的名称以及包装器是否应该动态构建此.json文件或使用已构建的一个,该文件可以是Google(标准下载)或开发人员创建的。
对于像PHPMailer这样的守护程序应用程序,客户端密钥授权(即用户授权)比授权代码(即用户)授权更适合。对于Microsoft,SMTP支持授权代码授权和客户端密钥(即应用程序)授权流程。
对于Google API,SMTP支持授权代码授权和客户端密钥(服务账户)授权(请参阅github存储库/library googleapis/google-api-php-client)。TheLeague的Google Gmail提供者仅支持授权代码授权,这由包装器支持。
如果提供者或客户端支持,包装器将自动实现$_SESSION 'state'交换和PKCE代码交换,以防止在授权代码流程中发生CSRF攻击。
使用包装器有三种非常不同的方法
a. 在其他方面“标准”的PHPMailer电子邮件应用程序中,将提供者的实例化(例如,oauth2-azure)和PHPMailer的OAuth2替换为SendOauth2B的实例化,使用PHPMailer的可选OAuthTokenProvider。SendOauth2B的调用将需要所有常规OAuth2参数,如clientID。来自电子邮件服务供应商的特定参数,如适当的范围参数,将由SendOauth2C自动提供(PHPMailer的示例文件夹有一个完整的示例应用程序)。
b. 与a.类似,但不需要为SendOauth2B提供大量的OAuth2参数(多达16个),只需传递(在你的代码中实例化的)PHPMailer对象以及所选的'认证集'的名称或编号。后者将在下面进行说明,它是为生成离线初始刷新令牌而创建的潜在多个认证参数组之一。新的刷新令牌和其他参数随后存储在一个记录文件中,供SendOauth2B稍后使用。
c. 完全用前端-SendOauth2A替换你的PHPMailer应用程序,它除了其他功能外,还会刷新Microsoft刷新令牌并将其写回到单个记录文件中,以便在下一次调用PHPMailer时使用。
本文件的其余部分基本上是针对b.和c.的;a.在PHPMailer仓库的示例应用程序中已涵盖。
为什么要包装? 通常,非平凡网站在多个位置(联系页面、购买确认、PayPal IPN等)使用电子邮件,在每个这样的页面中嵌入PHPMailer调用代码和邮件设置会使维护变得难以管理,尤其是如果OAuth2被设置为为每个点使用不同的客户端密钥——这是更安全的方法。此外,Microsoft刷新令牌的最大有效期为90天,在此期间发行者必须重新授权以获取新的令牌,除非在此期间他或她已授权延长现有令牌的有效期('90天'是最大不活动时间)。另一种方法是每次颁发访问令牌时都请求新的刷新令牌。
如上所述,使用完整的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月)需要更改两项代码
**注意(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取代。
**注意(2):如果你希望刷新令牌自动更新,需要在TheLeague oauth2-client/src/Token/AccessToken.php中添加一行代码。包装器会检查这个更改;如果不存在,包装器将默认允许刷新令牌(如果有的话;Google的通常是)以正常方式过期。现有的代码大约在第107行(确切行数取决于版本)如下
if (!empty($options['refresh_token'])) {
$this->refreshToken = $options['refresh_token'];
然后在}之前添加以下行
$_SESSION[__NAMESPACE__ . "\\updatedRefreshToken"] = $this->refreshToken;
2. 提供者
Microsoft提供者是Jan Hajek等人编写的networg * oauth2-azure *。Google API是由Google编写的“官方”客户端API。Gmail提供者是Woody Gilk等人编写的PHP League * oauth2-google *。
3. 类和文件
SendOauth2由四个PHP类和一个Trait组成,这些类名与PHP文件相同,默认存储在vendor/decomplexity/sendoauth2/src文件夹中。
还有三个文件分布在Examples文件夹中,应该移动到/vendor的父文件夹中,供开发人员修改。其中一个文件(SendOauth2D-settings)是用于验证最多五个电子邮件服务的模板:Microsoft 365 OAuth2、Microsoft 365基本身份验证(用户名和密码)、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 '需要'一个包含安全设置(例如clientId、clientSecret、redirectURI、服务提供商(Microsoft、Google)、身份验证类型(例如XOAUTH2)等)的SendOauth2D-settings.php文件。对于每个需要不同安全设置或不同提供程序(通常是一个网站页面)的PHPMailer调用,有一组这些安全设置。一个网站可以使用Microsoft和Google(以及添加到SendOauth2C的任何其他)的任何组合,以及任意数量的不同安全组。每个设置都有一个唯一的标识符(在模板SendOauth2D-settings中编号为1、2、3、4),但开发人员可以自由使用更有意义的东西。当SendOauth2D通过SendOauth2D-invoke实例化时(见下面的第7节),后者指定组号,然后SendOauth2D生成一个带有(对于OAuth2)刷新令牌和其他安全设置(例如客户端ID和客户端密钥)的“交换”文件。如果选择基本身份验证组,输出文件类似,但包含SMTP密码,不包括OAuth2设置。对于每组安全设置有一个交换文件。
当SendOauth2A从一个网页实例化时,例如,相关的组号传递给它。这反过来又传递给SendOauth2B,后者读取适当的安全设置交换文件。
客户端ID、客户端密钥、重定向URI和刷新令牌只需从Microsoft AAD或Google控制台.cloud复制到SendOauth2D设置的文件中。不需要将其复制到调用PHPMailer的代码中,因为它们在文件中已有。这也意味着如果需要,可以在之后将SendOauth2D设置的文件移动到更安全的地方,并且在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客户端凭据授权,应用程序必须作为服务主体注册(使用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设置的网络安全设置
- 选择一个合适的样本安全组(Microsoft XOAUTH2是第一个也是默认的)并插入您从Microsoft AAD或Google控制台.cloud复制的自己的设置。还有两个附加设置
- 'SMTPAddressDefault' - 将其设置为用户主体名称(例如,AAD管理员电子邮件地址)
- 'fromNameDefault' - 您想要用于电子邮件 *发件人* 名称的默认值。这两个都可以在调用SendOauth2A时进行覆盖。
6. SendOauth2D 实例化
用于实例化SendOauth2D的代码必须与您指定给Microsoft AAD或Google控制台.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’与‘mailAttach’类似,但它将文件(通常是图像)嵌入到‘mailText’电子邮件正文中(见下文)。每个‘mailAttachInline’数组参数有两个逗号分隔的值:文件名后面跟任意文本字符串(‘cid’)。然后电子邮件正文还必须包含一个HTML指示器,说明如何嵌入文件:其形式如下:
‘mailAttachString’和‘mailAttachStringInline’与‘mailAttach’和‘mailAttachInline’分别类似,但它们不是附加或嵌入文件,而是用位字符串附加或嵌入,这是mailAttachString参数的唯一值,以及mailAttachStringInline参数的两个逗号分隔值中的第一个。
-
‘mailText’字符串是电子邮件的HTML文本。
-
‘mailTextPlain’是可选的纯文本版本。如果没有指定‘mailTextPlain’,则通过从‘mailText’中删除HTML字符创建纯文本版本。
-
‘mailSMTPAddress’字符串覆盖在SendOauth2D中设置的默认SMTP‘username’(地址)。这仅在您希望覆盖‘SMTPAddressDefault’时才应在全局中设置。当SendOauth2D运行时,至少对于微软来说,当前登录的租户成员将被授权或请求成员登录以进行授权。这通常需要与SMTPAddressDefault中设置的电子邮件地址相同,否则SendOAuth2B身份验证将失败。如果用于基本身份验证,除非SMTP密码与SendOauthD中的switch case相同,否则将失败。为了提高安全性,全局不能覆盖后者,即全局没有可用的$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); }