如何在PHPMailer中为Exchange Online实现OAuth 2.0认证?
切换PHPMailer至Exchange Online OAuth2.0认证遇Bad Request错误排查
背景
微软已于2022年10月1日起弃用Exchange Online的基础认证(账号密码登录),现需将PHPMailer通过SMTP发送邮件的方式切换至OAuth 2.0认证。
原基础认证可用代码
<?php include "vendor/autoload.php"; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; $mail = new PHPMailer; $mail->IsSMTP(); $mail->Host = "smtp.office365.com"; $mail->Port = "587"; $mail->SMTPAuth = true; $mail->Username = "my_email@my-company.com"; $mail->Password = "my_password"; $mail->SMTPSecure = "tls"; $mail->From = "my_email@my-company.com"; $mail->FromName = "my_name"; $mail->AddAddress("my_email@my-company.com"); $mail->IsHTML(true); $mail->Subject = "This is a test subject"; $mail->Body = "Hello, how are you?"; $mail->Send(); ?>
尝试的OAuth 2.0代码(无法正常运行)
<?php include "vendor/autoload.php"; use PHPMailer\PHPMailer\Exception; use PHPMailer\PHPMailer\OAuth; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\SMTP; use Stevenmaguire\OAuth2\Client\Provider\Microsoft; $mail = new PHPMailer; $mail->isSMTP(); $mail->Host = "smtp.office365.com"; $mail->SMTPAuth = true; $mail->AuthType = "XOAUTH2"; $mail->SMTPDebug = SMTP::DEBUG_LOWLEVEL; $mail->SMTPSecure = "tls"; $mail->Port = 587; $username = "my_email@my-company.com"; $clientId = "client_id_from_azure_app_registration"; $clientSecret = "client_secret_from_azure_app_registration"; $redirectURI = "my_redirect_uri"; $Token = "my_token"; $mail->refresh_token = $Token; $provider = new Stevenmaguire\OAuth2\Client\Provider\Microsoft( [ "clientId" => $clientId, "clientSecret" => $clientSecret, "redirectUri" => $redirectURI ] ); $provider->urlAPI = "https://outlook.office365.com/.default"; $provider->scope = "Mail.Send"; $mail->setOAuth( new OAuth( [ "provider" => $provider, "clientId" => $clientId, "clientSecret" => $clientSecret, "refreshToken" => $Token, "userName" =>$username ] ) ); $mail->From = $username; $mail->AddAddress("my_email@my-company.com"); $mail->IsHTML(true); $mail->Subject = "This is a test subject"; $mail->Body = "Hello, how are you?"; $mail->Send(); ?>
报错信息
2022-09-27 13:51:38 Connection: opening to smtp.office365.com:587, timeout=300, options=array() 2022-09-27 13:51:38 Connection: opened 2022-09-27 13:51:38 SMTP INBOUND: "220 AS4P191CA0011.outlook.office365.com Microsoft ESMTP MAIL Service ready at Tue, 27 Sep 2022 13:51:37 +0000" 2022-09-27 13:51:38 SERVER -> CLIENT: 220 AS4P191CA0011.outlook.office365.com Microsoft ESMTP MAIL Service ready at Tue, 27 Sep 2022 13:51:37 +0000 2022-09-27 13:51:38 CLIENT -> SERVER: EHLO 2022-09-27 13:51:38 SMTP INBOUND: "250-AS4P191CA0011.outlook.office365.com Hello [2a02:4780:8:2::25]" 2022-09-27 13:51:38 SMTP INBOUND: "250-SIZE 157286400" 2022-09-27 13:51:38 SMTP INBOUND: "250-PIPELINING" 2022-09-27 13:51:38 SMTP INBOUND: "250-DSN" 2022-09-27 13:51:38 SMTP INBOUND: "250-ENHANCEDSTATUSCODES" 2022-09-27 13:51:38 SMTP INBOUND: "250-STARTTLS" 2022-09-27 13:51:38 SMTP INBOUND: "250-8BITMIME" 2022-09-27 13:51:38 SMTP INBOUND: "250-BINARYMIME" 2022-09-27 13:51:38 SMTP INBOUND: "250-CHUNKING" 2022-09-27 13:51:38 SMTP INBOUND: "250 SMTPUTF8" 2022-09-27 13:51:38 SERVER -> CLIENT: 250-AS4P191CA0011.outlook.office365.com Hello [2a02:4780:8:2::25]250-SIZE 157286400250-PIPELINING250-DSN250-ENHANCEDSTATUSCODES250-STARTTLS250-8BITMIME250-BINARYMIME250-CHUNKING250 SMTPUTF8 2022-09-27 13:51:38 CLIENT -> SERVER: STARTTLS 2022-09-27 13:51:38 SMTP INBOUND: "220 2.0.0 SMTP server ready" 2022-09-27 13:51:38 SERVER -> CLIENT: 220 2.0.0 SMTP server ready 2022-09-27 13:51:38 CLIENT -> SERVER: EHLO 2022-09-27 13:51:38 SMTP INBOUND: "250-AS4P191CA0011.outlook.office365.com Hello [2a02:4780:8:2::25]" 2022-09-27 13:51:38 SMTP INBOUND: "250-SIZE 157286400" 2022-09-27 13:51:38 SMTP INBOUND: "250-PIPELINING" 2022-09-27 13:51:38 SMTP INBOUND: "250-DSN" 2022-09-27 13:51:38 SMTP INBOUND: "250-ENHANCEDSTATUSCODES" 2022-09-27 13:51:38 SMTP INBOUND: "250-AUTH LOGIN XOAUTH2" 2022-09-27 13:51:38 SMTP INBOUND: "250-8BITMIME" 2022-09-27 13:51:38 SMTP INBOUND: "250-BINARYMIME" 2022-09-27 13:51:38 SMTP INBOUND: "250-CHUNKING" 2022-09-27 13:51:38 SMTP INBOUND: "250 SMTPUTF8" 2022-09-27 13:51:38 SERVER -> CLIENT: 250-AS4P191CA0011.outlook.office365.com Hello [2a02:4780:8:2::25]250-SIZE 157286400250-PIPELINING250-DSN250-ENHANCEDSTATUSCODES250-AUTH LOGIN XOAUTH2250-8BITMIME250-BINARYMIME250-CHUNKING250 SMTPUTF8 2022-09-27 13:51:38 Auth method requested: XOAUTH2 2022-09-27 13:51:38 Auth methods available on the server: LOGIN,XOAUTH2 Fatal error: Uncaught League\OAuth2\Client\Provider\Exception\IdentityProviderException: Bad Request in /home/u760208683/vendor/stevenmaguire/oauth2-microsoft/src/Provider/Microsoft.php:79 Stack trace: #0 /home/u760208683/vendor/league/oauth2-client/src/Provider/AbstractProvider.php(628): Stevenmaguire\OAuth2\Client\Provider\Microsoft->checkResponse(Object(GuzzleHttp\Psr7\Response), Array) #1 /home/u760208683/vendor/league/oauth2-client/src/Provider/AbstractProvider.php(537): League\OAuth2\Client\Provider\AbstractProvider->getParsedResponse(Object(GuzzleHttp\Psr7\Request)) #2 /home/u760208683/vendor/phpmailer/phpmailer/src/OAuth.php(115): League\OAuth2\Client\Provider\AbstractProvider->getAccessToken(Object(League\OAuth2\Client\Grant\RefreshToken), Array) #3 /home/u760208683/vendor/phpmailer/phpmailer/src/OAuth.php(128): PHPMailer\PHPMailer\OAuth->getToken() #4 /home/u760208683/vendor/phpmailer/phpmailer/src/SMTP.php(598): PHPMailer\PHPMailer\OAuth->getOauth64() #5 /home/u760208683/vendor/phpmailer/phpmailer/src/PHPMailer in /home/u760208683/vendor/stevenmaguire/oauth2-microsoft/src/Provider/Microsoft.php on line 79
解决方向
- 确认令牌类型:代码中
$Token必须是刷新令牌(refresh token),而非Postman生成的短期访问令牌(access token)。访问令牌无法用于刷新获取新令牌,会直接导致Bad Request。 - 修正Provider配置:
- 移除手动设置的
$provider->urlAPI,该库默认已配置正确的微软令牌端点,手动修改会导致请求地址错误。 - 在实例化Provider时以数组形式传入
scope,而非事后赋值:$provider = new Stevenmaguire\OAuth2\Client\Provider\Microsoft([ "clientId" => $clientId, "clientSecret" => $clientSecret, "redirectUri" => $redirectURI, "scope" => ["Mail.Send", "offline_access"] // 需包含offline_access以获取可刷新的令牌 ]);
- 移除手动设置的
- 检查Azure应用配置:
- 确保应用已添加Delegated类型的
Mail.Send权限,且完成了管理员同意(若为租户级应用)。 - 确认代码中的
redirectURI与Azure应用注册里配置的重定向URI完全一致,包括协议(http/https)、端口、路径。
- 确保应用已添加Delegated类型的
- 移除冗余代码:删除
$mail->refresh_token = $Token;,OAuth实例已通过构造参数传入refreshToken,无需重复设置。 - 验证令牌获取流程:若使用授权码流程获取refresh token,需确保授权请求中包含
offline_accessscope,否则无法生成可用于刷新的令牌。
内容的提问来源于stack exchange,提问作者Laurents_Gits
相关产品推荐
相关产品推荐

