PHPMailer + Gmail OAuth2 寄信

小蛙有個維護的系統使用 PHPMailer 透過 Gmail 帳號密碼發送郵件,近期收到 Google 寄來的通知「自 2024 年 9 月 30 日起,應用程式必須採用 OAuth 才能存取 Google Workspace 帳戶。系統將不再支援使用密碼 (應用程式密碼除外) 的存取行為。」為了避免哪一天突然通通都只支援 OAuth,趁現在有一點時間趕快來改一下。

PHPMailer + Gmail OAuth 發信大概有幾個步驟:

  1. 安裝必要套件:使用 composer 在網站上安裝 phpmailer/phpmailer 及 league/oauth2-google 套件。
  2. 建立 OAuth 2.0 用戶端 ID 憑證:到 Goolge Cloud 建立一個專案,並開啟 Gmail API 與建立一個 OAuth 2.0 用戶端 ID 憑證。
  3. 取得 Refresh Token:設定 get_oauth_token.php 通過 OAuth 驗證,取得 Refresh Token。
  4. 使用 PHPMailer 寄信:使用 PHPMailer 搭配前幾步得到的資訊寄送郵件。

安裝必要套件

需使用 composer 安裝必要套件,沒有 composer 的話可參考這邊

composer require phpmailer/phpmailer
composer require league/oauth2-google

建立 OAuth 2.0 用戶端 ID 憑證

啟用 Gmail API

進入到 Google Cloud,點擊左上方專案下拉選單 -> 新增專案 -> 輸入專案名稱,只要自己能辨識的即可,下面若有機構、位置,也都必須填寫上去

PHPMailer + Gmail OAuth 寄信 1

專案建立完成後進入該專案,左側選擇「已啟用的 API 和服務」,點擊「啟用 API 和服務」

PHPMailer + Gmail OAuth 寄信 2

找到並啟用 Gmail API

PHPMailer + Gmail OAuth 寄信 3

建立 OAuth 用戶端 ID 憑證

成功啟用 Gmail API 後,左側進入「憑證」選單 -> 點擊「建立憑證」 -> OAuth 用戶端 ID

PHPMailer + Gmail OAuth 寄信 4

第一次建立需要先設定同意畫面,點擊「設定同意畫面」

PHPMailer + Gmail OAuth 寄信 5

我們的目的是完成至少一次 OAuth 驗證取得 Refresh Token,這邊選「內部」就可以了

PHPMailer + Gmail OAuth 寄信 6

應用程式名稱、使用者支援電子郵件及開發人員聯絡資訊為必填項目,其餘部份視情況填寫,填寫完成後點擊「儲存並繼續」。

PHPMailer + Gmail OAuth 寄信 7
PHPMailer + Gmail OAuth 寄信 8

第二步範圍部份直接留空,點擊「儲存並繼續」

PHPMailer + Gmail OAuth 寄信 9
PHPMailer + Gmail OAuth 寄信 10

OAuth 同意畫面建立完成

PHPMailer + Gmail OAuth 寄信 11
PHPMailer + Gmail OAuth 寄信 12

再回到「憑證」->「建立憑證」->「OAuth 用戶端 ID」

PHPMailer + Gmail OAuth 寄信 13

應用程式類型選擇「網頁應用程式」,名稱為必填,可以跟小蛙一樣輸入 PHPMailer,只要能辨識即可。「已授權的重新導向 URI」填入網站下 phpmailer 套件下的 get_oauth_token.php 路徑 (也可以把它拉出來到外層比較好處理的地方),小蛙這邊的例子是

https://xx.xxx/vendor/phpmailer/phpmailer/get_oauth_token.php
PHPMailer + Gmail OAuth 寄信 14

建立完成後會得到用戶端編號用戶端密鑰,先存起來等一下馬上會用到。

PHPMailer + Gmail OAuth 寄信 15

取得 Refresh Token

找到 PHPMailer 下的 get_oauth_token.php (預設應該會在網頁目錄下的 vender/phpmailer/phpmailer/),PHPMailer 原始附帶的檔案會一直卡在 Provider missing 或 Invalid state,沒辦法正確取得 Refresh Token,因此小蛙對 get_oauth_token.php 做了一些修改,確保可以拿到 token。

<?php
/**
 * PHPMailer - PHP email creation and transport class.
 * PHP Version 5.5
 * @package PHPMailer
 * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
 * @author Marcus Bointon (Synchro/coolbru) <[email protected]>
 * @author Jim Jagielski (jimjag) <[email protected]>
 * @author Andy Prevost (codeworxtech) <[email protected]>
 * @author Brent R. Matzelle (original founder)
 * @copyright 2012 - 2020 Marcus Bointon
 * @copyright 2010 - 2012 Jim Jagielski
 * @copyright 2004 - 2009 Andy Prevost
 * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
 * @note This program is distributed in the hope that it will be useful - WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * Get an OAuth2 token from an OAuth2 provider.
 * * Install this script on your server so that it's accessible
 * as [https/http]://<yourdomain>/<folder>/get_oauth_token.php
 * e.g.: http://localhost/phpmailer/get_oauth_token.php
 * * Ensure dependencies are installed with 'composer install'
 * * Set up an app in your Google/Yahoo/Microsoft account
 * * Set the script address as the app's redirect URL
 * If no refresh token is obtained when running this file,
 * revoke access to your app and run the script again.
 */

namespace PHPMailer\PHPMailer;

// 用來看錯誤訊息以及刪除 opcache
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
@opcache_reset();
	
/**
 * Aliases for League Provider Classes
 * Make sure you have added these to your composer.json and run `composer install`
 * Plenty to choose from here:
 * @see https://oauth2-client.thephpleague.com/providers/thirdparty/
 */
//@see https://github.com/thephpleague/oauth2-google
use League\OAuth2\Client\Provider\Google;

// 根據自己的 vendor 路徑進行調整
require '../../autoload.php';

session_start();

// 改成剛剛在 Google Cloud 上設定的值
$providerName = 'Google';
// 用戶端編號
$clientId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com';
// 用戶端密碼
$clientSecret = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
// 已授權的重新導向 URI
$redirectUri = 'https://xxx.xxxx.xxx/get_oauth_token.php';

$_SESSION['provider'] = $providerName;
$_SESSION['clientId'] = $clientId;
$_SESSION['clientSecret'] = $clientSecret;

$params = [
    'clientId' => $clientId,
    'clientSecret' => $clientSecret,
    'redirectUri' => $redirectUri,
    'accessType' => 'offline'
];

$options = [];
$provider = null;

switch ($providerName) {
    case 'Google':
        $provider = new Google($params);
        $options = [
            'scope' => [
                'https://mail.google.com/'
            ]
        ];
        break;
}

if (null === $provider) {
    exit('Provider missing');
}

if (!isset($_GET['code'])) {
    //If we don't have an authorization code then get one
    $authUrl = $provider->getAuthorizationUrl($options);
    $_SESSION['oauth2state'] = $provider->getState();
    header('Location: ' . $authUrl);
    exit;
    //Check given state against previously stored one to mitigate CSRF attack
	// 這段不註解掉的話,會一直卡在 Invalid state,沒辦法拿到正確的 token
/*} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
	error_log("3333");
	error_log("state 3333 " . $_SESSION['oauth2state']);
    //unset($_SESSION['oauth2state']);
    //unset($_SESSION['provider']);
    exit('Invalid state');*/
} else {
    //unset($_SESSION['provider']);
    //Try to get an access token (using the authorization code grant)
    $token = $provider->getAccessToken(
        'authorization_code',
        [
            'code' => $_GET['code']
        ]
    );
    //Use this to interact with an API on the users behalf
    //Use this to get a new access token if the old one expires
    echo 'Refresh Token: ', htmlspecialchars($token->getRefreshToken());
}

將內容貼上後,開啟瀏覽器連到該網址 (就是已授權的重新導向 URI) 開始進入 OAuth 登入流程,選擇要登入的帳號

PHPMailer + Gmail OAuth 寄信 16

確認登入

PHPMailer + Gmail OAuth 寄信 17

信任應用程式

PHPMailer + Gmail OAuth 寄信 18

最後會印出 Refresh Token: xxxxxxxxxx,這個 token 一定要存好,少了這個 PHPMailer 就沒辦法透過 OAuth 驗證寄信喔,原本以為 get_oauth_token.php 每次執行應該 “有機會” 去換一個可使用的 Token 回來,不過再執行一次會發現 Refresh Token: 變成空白。

AP1GczMAEzc9EkM0XaVMs8px7YKL3EXAUJ2L uJ8YlSTBBnEJAP8BH4JqC2s b5 Rm1LNffiyRiTBmehd0jhvLbPhpAslSkq 8Lx1BfxwSTfg1jLVcAgOcadwiOl V0YmZzKy86GplsW1NK61FBz9sCKGyCH=w861 h72 s rw gm PHPMailer + Gmail OAuth2 寄信

到這邊就成功取得可以使用的 Token 了,回到 PHPMailer 寄信程式吧!

使用 PHPMailer 寄信

複製下面的檔案,視自身情況修改幾個地方:

  1. require(‘./vendor/autoload.php’);:將位置指向正確的路徑。
  2. $client_id:前面步驟取得的用戶端編號。
  3. $client_secret:前面步驟取得的用戶端密碼。
  4. $refresh_token:前面步驟取得的 Token。
  5. $email:前面步驟通過 OAuth 的信箱。
  6. 信件相關之寄件人、收件人、主旨、內容 … 等。
<?php

// 用來看錯誤訊息以及刪除 opcache
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
@opcache_reset();

// 根據自己的路徑修改 composer 的載入位置
require('./vendor/autoload.php');

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
use League\OAuth2\Client\Provider\Google;

// 用戶端編號
$client_id 	= 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com';
// 用戶端密碼
$client_secret 	= 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
// 從前一個步驟取得的 Token
$refresh_token  = '1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxg';
// OAuth 的信箱
$email		= 'xxxxxxx@xxxxxxx';

$mail = new PHPMailer(true);

$provider = new Google(
	[
		'clientId' => $client_id,
		'clientSecret' => $client_secret,
	]
);

$mail->setOAuth(
	new OAuth(
		[
			'provider'     => $provider,
			'clientId'     => $client_id,
			'clientSecret' => $client_secret,
			'refreshToken' => $refresh_token,
			'userName'     => $email,
		]
	)
);

$mail->isSMTP();

$mail->SMTPDebug 	= 3;				// 如果需要顯示debug內容,測完確定沒問題請改成 0
$mail->SMTPAuth 	= true; 
$mail->SMTPSecure 	= 'tls';
$mail->AuthType 	= 'XOAUTH2';
$mail->Host 		= "smtp.gmail.com";
$mail->Port 		= 587;
$mail->CharSet 		= "utf-8";

$mail->setFrom('寄件人信箱', '寄件人名稱');    // 寄件人
$mail->addAddress('收件人信箱', '收件人名稱'); // 收件人
$mail->isHTML(true);                         // 寄送 HTML 格式郵件

$mail->Subject = "郵件主旨";                  // 主旨
$mail->Body    = "<Strong>測試</Strong>";     // 信件內容
$mail->AltBody = "測試";                      // 無法顯示 HTML 時的內容

$mail->send();

?>

一切就緒後連到該寄信的 PHP 頁面,就可以成功把信寄出囉!

PHPMailer + Gmail OAuth 寄信 19
延伸閱讀:Google 試算表批量發送 Email (免費版每日 50 封)

    發佈留言

    發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

    這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料