使用PHPMailer调用Microsoft OAuth令牌时出现invalid_client错误
我的Microsoft配置
- 认证设置:[链接]
- API权限:[链接]
- 令牌配置:为空
- 公开API:为空
获取令牌的代码(运行正常)
new \Stevenmaguire\OAuth2\Client\Provider\Microsoft([ 'clientId' => "my_id...", 'clientSecret' => "my_secret...", 'redirectUri' => "https://example.com/get/get_oauth_token.php", 'accessType' => 'offline', // Optional (what is this doing??) 'urlAuthorize' => 'https://login.windows.net/common/oauth2/authorize', 'urlAccessToken' => 'https://login.windows.net/common/oauth2/token', 'urlResourceOwnerDetails' => 'https://outlook.office.com/api/v1.0/me' ]); $authUrl = $provider->getAuthorizationUrl([ 'scope' => ['wl.basic', 'wl.signin', 'wl.emails'] // 不清楚需要哪些权限,是否要和应用权限对应? ]); $_SESSION['oauth2state'] = $provider->getState(); header('Location: ' . $authUrl);
使用令牌发送邮件的代码
$mail = new PHPMailer(); $mail->isSMTP(); $mail->Timeout = 10; $mail->Host = "smtp.office365.com"; // smtp.office365.com、outlook.office.com、smtp-mail.outlook.com到底用哪个?试了都没用 $mail->Port = 587; $mail->SMTPSecure = "tls"; $mail->SMTPAuth = true; $mail->AuthType = 'XOAUTH2'; // 省略邮件内容相关代码 $provider = new \Stevenmaguire\OAuth2\Client\Provider\Microsoft([ 'clientId' => "my_id...", 'clientSecret' => "my_secret...", 'redirectUri' => "https://example.com/get/get_oauth_token.php" // 这里需要配置吗? ]); // 将OAuth提供者实例传递给PHPMailer $mail->setOAuth( new OAuth([ 'provider' => $provider, 'clientId' => "my_id...", 'clientSecret' => "my_secret...", 'refreshToken' => "my_refreshToken...", // 'oauthToken' => $token, // 这里是不是完全忽略了实际的access token?需要更新refresh token吗? 'userName' => 'me@mycompany.onmicrosoft.com' // 应该是授权的用户邮箱? ]) ); $mail->send(); $mail->SmtpClose();
出现的错误
2022-09-22 17:49:08 SERVER -> CLIENT: 220 FR0P281CA0070.outlook.office365.com Microsoft ESMTP MAIL Service ready at Thu, 22 Sep 2022 17:49:07 +0000 2022-09-22 17:49:08 CLIENT -> SERVER: EHLO example.com 2022-09-22 17:49:08 SERVER -> CLIENT: 250-FR0P281CA0070.outlook.office365.com Hello [85.13.129.169]250-SIZE 157286400250-PIPELINING250-DSN250-ENHANCEDSTATUSCODES250-STARTTLS250-8BITMIME250-BINARYMIME250-CHUNKING250 SMTPUTF8 2022-09-22 17:49:08 CLIENT -> SERVER: STARTTLS 2022-09-22 17:49:08 SERVER -> CLIENT: 220 2.0.0 SMTP server ready 2022-09-22 17:49:08 CLIENT -> SERVER: EHLO example.com 2022-09-22 17:49:08 SERVER -> CLIENT: 250-FR0P281CA0070.outlook.office365.com Hello [85.13.129.169]250-SIZE 157286400250-PIPELINING250-DSN250-ENHANCEDSTATUSCODES250-AUTH LOGIN XOAUTH2250-8BITMIME250-BINARYMIME250-CHUNKING250 SMTPUTF8 Bad Request /www/htdocs/w014af32/me/vendor/stevenmaguire/oauth2-microsoft/src/Provider/Microsoft.php(79) #0 /www/htdocs/w014af32/me/vendor/league/oauth2-client/src/Provider/AbstractProvider.php(628): Stevenmaguire\OAuth2\Client\Provider\Microsoft->checkResponse(Object(GuzzleHttp\Psr7\Response), Array) #1 /www/htdocs/w014af32/me/vendor/league/oauth2-client/src/Provider/AbstractProvider.php(537): League\OAuth2\Client\Provider\AbstractProvider->getParsedResponse(Object(GuzzleHttp\Psr7\Request)) #2 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/OAuth.php(115): League\OAuth2\Client\Provider\AbstractProvider->getAccessToken(Object(League\OAuth2\Client\Grant\RefreshToken), Array) #3 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/OAuth.php(128): PHPMailer\PHPMailer\OAuth->getToken() #4 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/SMTP.php(598): PHPMailer\PHPMailer\OAuth->getOauth64() #5 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/PHPMailer.php(2190): PHPMailer\PHPMailer\SMTP->authenticate('', '', 'XOAUTH2', Object(PHPMailer\PHPMailer\OAuth)) #6 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/PHPMailer.php(2003): PHPMailer\PHPMailer\PHPMailer->smtpConnect(Array) #7 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/PHPMailer.php(1662): PHPMailer\PHPMailer\PHPMailer->smtpSend('Date: Thu, 22 S...', 'test content\r\n') #8 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/PHPMailer.php(1500): PHPMailer\PHPMailer\PHPMailer->postSend() #9 /www/htdocs/w014af32/me/includes/helpers/mail.php(182): PHPMailer\PHPMailer\PHPMailer->send() #10 /www/htdocs/w014af32/me/includes/helpers/mail.php(113): mailSendSingle(Object(PHPMailer\PHPMailer\PHPMailer), Array, true) #11 /www/htdocs/w014af32/me/get/test4.php(25): mailSend(Array) #12 {main} # Args: checkResponse: [{},{"error":"invalid_client","error_description":"The client does not exist or is not enabled for consumers. If you are the application developer, configure a new application through the App Registrations in the Azure Portal at https:\/\/go.microsoft.com\/fwlink\/?linkid=2083908.","correlation_id":"4dc377bd-0d5d-4444-bdd4-82b862f86177"}] getParsedResponse: [{}] getAccessToken: [{},{"refresh_token":"my refreshToken..."}] authenticate: ["","","XOAUTH2",{}] smtpConnect: [{"ssl":{"verify_peer":false,"verify_peer_name":false,"allow_self_signed":true}}] smtpSend: ["Date: Thu, 22 Sep 2022 19:49:08 +0200\r\nTo: John Doe <me@example.com>\r\nFrom: John Doe <me@mycompany.onmicrosoft.com>\r\nReply-To: John Doe <me@mycompany.onmicrosoft.com>\r\nSubject: test mail\r\nMessage-ID: <AAtH5DrcPQ9Fn4PysOdjIt0OnDbByC4eaRYVnJh33M@example.com>\r\nX-Mailer: PHPMailer 6.6.4 (https:\/\/github.com\/PHPMailer\/PHPMailer)\r\nMIME-Version: 1.0\r\nContent-Type: text\/html; charset=utf-8\r\n\r\n","test content\r\n"] mailSendSingle: [{"Priority":null,"CharSet":"utf-8","ContentType":"text\/html","Encoding":"7bit","ErrorInfo":"","From":"me@mycompany.onmicrosoft.com","FromName":"John Doe","Sender":"me@mycompany.onmicrosoft.com","Subject":"test mail","Body":"test content","AltBody":"","Ical":"","WordWrap":0,"Mailer":"smtp","Sendmail":"\/usr\/sbin\/sendmail","UseSendmailOptions":true,"ConfirmReadingTo":"","Hostname":"","MessageID":"","MessageDate":"","Host":"smtp.office365.com","Port":"587","Helo":"","SMTPSecure":"tls","SMTPAutoTLS":true,"SMTPAuth":true,"SMTPOptions":{"ssl":{"verify_peer":false,"verify_peer_name":false,"allow_self_signed":true}},"Username":"","Password":"","AuthType":"XOAUTH2","Timeout":10,"dsn":"","SMTPDebug":2,"Debugoutput":"html","SMTPKeepAlive":false,"SingleTo":false,"do_verp":false,"AllowEmpty":false,"DKIM_selector":"","DKIM_identity":"","DKIM_passphrase":"","DKIM_domain":"","DKIM_copyHeaderFields":true,"DKIM_extraHeaders":[],"DKIM_private":"","DKIM_private_string":"","action_function":"","XMailer":""},{"from_mail":"me@mycompany.onmicrosoft.com","from_name":"John Doe","smtp_profile":3,"from_user":1,"to_mail":"me@example.com","to_name":"John Doe","to_type":"staff","to_id":3,"template":0,"debug":true,"testing":true,"subject":"test mail","type":"test","date":"2022-09-22 19:49:08","content":"test content","reply_to":"me@mycompany.onmicrosoft.com"},true] mailSend: [{"from_mail":"me@mycompany.onmicrosoft.com","from_name":"John Doe","smtp_profile":3,"from_user":1,"to_mail":"me@example.com","to_name":"John Doe","to_type":"staff","to_id":3,"template":0,"debug":true,"testing":true,"subject":"test mail","type":"test","date":"2022-09-22 19:49:08","content":"test content","reply_to":"me@mycompany.onmicrosoft.com"}] 2022-09-22 17:49:08 CLIENT -> SERVER: QUIT 2022-09-22 17:49:08 SERVER -> CLIENT: 221 2.0.0 Service closing transmission channel
疑问
令牌获取流程正常,推测问题出在Azure配置中。获取令牌的用户是全局管理员角色成员,应用所有者是另一个用户。请问如何解决?
从错误信息里的invalid_client和描述“客户端不存在或未对消费者启用”,结合你的场景,按以下步骤排查:
确认应用程序类型与端点匹配
你当前使用的是旧版Azure AD端点(login.windows.net),但如果应用注册为单租户类型,却使用common端点(允许所有租户用户登录)会导致验证失败:- 单租户应用:将授权和令牌端点中的
common替换为你的租户ID,例如https://login.windows.net/你的租户ID/oauth2/authorize - 多租户应用:确保注册时选择了“任何组织目录中的账户”或“任何组织目录中的账户和个人Microsoft账户”
- 单租户应用:将授权和令牌端点中的
核对客户端ID与密钥
确认发送邮件代码中的clientId和clientSecret完全匹配Azure门户中应用注册的“客户端ID”和“客户端密码”(注意密码是创建时生成的字符串,不是密码ID)。更新API权限
你当前使用的wl.basic等是旧版Live Connect权限,不适用于Office365 SMTP。需要添加Exchange Online的委托权限:- 进入应用注册的“API权限”页面,添加Microsoft Graph的
Mail.Send权限 - 由全局管理员点击“授予管理员同意”按钮完成权限授权
- 进入应用注册的“API权限”页面,添加Microsoft Graph的
修正授权范围
获取令牌时的scope应替换为https://outlook.office.com/Mail.Send或https://graph.microsoft.com/Mail.Send,删除原来的wl.basic等无效权限。确认应用启用状态
在Azure门户的应用注册页面,进入“管理”→“属性”,确保对应类型的应用开关已启用:- 桌面/移动应用:开启“启用公众客户端流”
- Web应用:确保重定向URI配置完全正确(和代码中的
redirectUri一致)
简化发送邮件代码的配置
发送邮件时的redirectUri可以省略,PHPMailer通过refreshToken获取accessToken时不需要该参数。
另外,PHPMailer的SMTP主机用smtp.office365.com、端口587、TLS设置都是正确的,无需调整。
内容的提问来源于stack exchange,提问作者br4nnigan

