如何通过Gmail SMTP安全地发送电子邮件

10
以下代码 https://dev59.com/7XA65IYBdhLWcg3wxBer#3649148 在Google更改其安全政策后,一直能正常工作,直到最近出现故障。
我收到了来自Google的邮件:

您好xxx,有人刚刚尝试使用不符合现代安全标准的应用程序从您的Google帐户 xxx@gmail.com 登录。

我们强烈建议您使用安全应用程序(如Gmail)访问您的帐户。所有由Google制作的应用程序都符合这些安全标准。另一方面,使用较不安全的应用程序可能会使您的帐户容易受到攻击。了解更多信息。

Google已阻止此登录尝试,但您应该查看最近使用的设备:

我查看了https://support.google.com/accounts/answer/6010255?hl=en-GB 我想知道实现正确登录尝试的方法是什么,以便继续通过Gmail SMTP发送电子邮件,而无需用户端进行任何配置?

1
你是否曾经找到一种安全连接方式,而不必启用不安全的应用程序通过SMTP发送Gmail电子邮件? - Nepaluz
@Nepaluz 有点晚了,但我必须实现它,所以我添加了以下解决方案。有些东西有点棘手(有时谷歌文档已经过时),但并不难。我包括了我必须处理的所有棘手部分。如果您想用不同的语言完成此操作,有很多链接可供参考。 - Jiří
2个回答

2

OAuth 2.0

因为我们的服务器没有邮件服务器,所以我不得不在基于PHP的网页上实现这个功能,而是使用谷歌的邮件服务器。未经授权访问他们的服务可能会被谷歌禁止,因此我们需要一个具有未来性的解决方案。我相信将此解决方案移植到其他编程语言(如Java)应该不是什么大问题。

要求:

谷歌邮箱账户启用了云控制台,启用了https的Web域名,PHP 5.4或更高版本,并且安装了JSON扩展(自v5.2起捆绑在一起,但有时未安装 - 我们假设它已经安装)。还需要PHP Google API客户端库,可以通过以下方式获取:

  • 服务器的命令行界面(CLI)和Composer依赖管理器。优点是您将自动接收更新。Google API库非常庞大,有方法可以排除不需要的部分。

或者

  • 从其存储库手动复制库。优点是您只需要访问您的Web托管云服务,这在您无法使用Composer管理器的情况下非常有用。

1)Google控制台中的项目

首先,您需要在Google控制台中创建一个项目。 在与您的网页邮件发送应用程序相关联的帐户上执行此设置 - 是的,从谷歌的角度来看,这将是一个应用程序。 登录后:在项目选择的标题菜单中,选择并创建一个新项目(我的项目现在称为Gmail API)。我还将使用一个(不存在的)名为gmapi.xy的网页。

console

创建新项目后,进入库部分,找到Gmail API并启用它。

2)凭据

显然,您需要一些身份验证数据。启用API应该会将您重定向到Gmail API控制台界面 - 在左侧菜单中选择凭据

创建新的OAuth客户端ID

  • 但是您被告知要在OAuth同意屏幕上首先指定您的产品标题 - 选择创建新的OAuth客户端ID将在左侧菜单中显示同意屏幕选项卡。

2 a)创建产品(OAuth同意屏幕)

您需要填写名称、支持电子邮件、范围、授权域和OAuth限制

范围: 您需要指定授予任何将认证到Gmail API的人的所有权限:

  • 点击“添加范围”
  • 向下滚动并在Gmail API部分启用所有所需的范围(在我的情况下,我只想发送邮件,因此我还启用了https://www.googleapis.com/auth/gmail.send范围)。

授权域名: 填写您想要从中发送电子邮件的域名:

  • 添加授权域 - 这些将被允许访问API(没有协议,仅顶级:gmapi.xy
  • 添加应用主页、隐私政策和服务条款链接 - 在我的情况下同上(但是完整地址:https://gmapi.xyhttps://gmapi.xy/policy等)

OAuth授权限制: 我对默认设置感到满意,因此没有更改(有关此主题的更多信息,请参见https://developers.google.com/analytics/devguides/config/mgmt/v3/limits-quotas

保存更改: 保存表单。我们要求额外的范围 - 可能会看到警告:

此应用程序未经验证。

向用户呈现OAuth同意屏幕可能显示警告“此应用程序未经验证”,如果它正在请求提供对敏感用户数据的访问权限的范围。这些应用程序必须最终通过验证过程来删除该警告和其他限制。在开发阶段,您可以通过单击高级>转到{项目名称}(不安全)继续进行此警告。

一旦您的应用程序正在运行、公开且不处于开发阶段,请提交应用程序以进行验证(在保存旁边的按钮)。您的新应用程序的验证可能需要多达几周

2 b) 再次创建凭据

现在我们可以创建凭据 - 与以前一样,在左侧菜单中选择凭据。创建新的OAuth客户端ID

  • 选择“Web应用程序”作为类型
  • 在“授权的 JavaScript 来源”中填写您的 URI。在我的情况下,是https://gmapi.xyhttps://www.gmapi.xy
  • 填写“授权重定向 URI”- Google 将要求用户授予权限,您必须指定允许 Google API 重定向回的 URI。在我的情况下,我正在使用test.php(具有https://www.gmapi.xy/test.phpURI),该文件被重定向到自身。因此,我将在那里添加https://www.gmapi.xy/test.phphttps://gmapi.xy/test.php.

允许重定向 URL 存在许多问题。如果您的真实 URI 没有斜杠符号,则不能以/符号结尾;如果使用非默认端口,则必须指定端口号等. 更多信息请参见此线程。 以下是您需要检查的清单:

  • http 还是 https?
  • & 还是 &?
  • 末尾斜杠(/)还是打开状态?
  • CMD/CTRL)+F,在凭据页面中查找完全匹配项。如果没有找到,则搜索缺失的项目。
  • 等待 Google 刷新。如果您经常进行更改,则可能每半个小时会发生,否则可能会停留在池中。对于我的情况,它几乎需要半个小时才能生效。
  • 是否在更改 OAuth ID 中的值后重新导入了credentials.json文件?

注意:拥有多个允许的 URI 可能会损害脚本——在成功授权后,重定向回您的网页后仍然可能会出现此错误

  • 保存表单(您不必复制成功创建 OAuth ID 后提供给您的凭据 - 关闭弹窗)
  • 下载JSON凭据文件- 在OAuth 2.0客户端ID部分,单击箭头图标

3 a) 使用Composer设置PHP

  • 通过Composer安装Google API库:composer require google/apiclient:^2.0
  • 可选:运行Google_Task_Composer::cleanup任务,并在composer.json中指定要保留的服务:
    {
        "require": {
            "google/apiclient": "^2.7"
        },
        "scripts": {
            "post-update-cmd": "Google_Task_Composer::cleanup"
        },
        "extra": {
            "google/apiclient-services": [
                "Drive",
                "YouTube"
            ]
        }
    }

注意:目前这种方法似乎存在问题,尚未解决。因此,我没有尝试这个功能。我也找不到可用的服务名称列表,所以如果有人找到并添加了链接,那就好了。

3 b) 设置 PHP - 不使用 Composer

发布版本中下载您需要的 PHP 版本的任何 .zip(稳定版本),并将其放置在网站的某个位置(上传.zip并在那里解压缩它)。

4 包含库、凭据和准备认证

也要上传 JSON 凭证文件。

安全提示:请将其放置在安全的地方:要么除了所有者外,删除任何人的 R/W 权限,要么使用 .htaccess 来保护此文件!

然后只需包含 autoload.php(为其路径在服务器上查找库,应在 vendor 文件夹中),并提供用于身份验证的凭据。

require_once '/path/to/google-api-php-client/vendor/autoload.php';

$client = new Google_Client();
$client->setAuthConfig('/path/to/client_credentials.json');

此外,您需要请求范围特权并从服务中获取一个token。可用的范围列表在这里。此外,请参见此线程以获取更多关于权限范围的示例。
$client->setPrompt("consent");
$client->setScopes(array(
    'https://www.googleapis.com/auth/gmail.send'
     //add more if you want to have them, or add
     // "https://mail.google.com/" to read, compose, send, delete mails
));
$client->setAccessType('offline');
$client->setIncludeGrantedScopes(true);
$tokenPath = 'where/you/want/to/store/token.json';


// Get new token - see redirect below
if (isset($_GET['code'])) {
    $accessToken = $client->fetchAccessTokenWithAuthCode($_GET['code']);
    // Save the token to a file.
    if (!file_exists(dirname($tokenPath))) {
        mkdir(dirname($tokenPath), 0700, true);
    }
    file_put_contents($tokenPath, json_encode($accessToken));
    $client->setAccessToken($accessToken);
} else if (file_exists($tokenPath)) {
    // Get the saved token
    $accessToken = json_decode(file_get_contents($tokenPath), true);
    $client->setAccessToken($accessToken);
}

// If there is no previous token or it's expired.
if ($client->isAccessTokenExpired()) {
    // Refresh the token if possible, else fetch a new one.
    $refreshToken = $client->getRefreshToken();
    if ($refreshToken) {
        $client->fetchAccessTokenWithRefreshToken($refreshToken);
    } else {
        // Get the token - redirect to the same page
        $redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
        $client->setRedirectUri($redirect_uri);
        $auth_url = $client->createAuthUrl();

        // Actually GO to the authentication (and authorization) url
        header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
    }
}

起初,网页会将我们重定向至谷歌页面,在那里我们需要授权允许应用程序使用所需的电子邮件地址发送邮件。一旦获准,就会创建一个token文件并将其存储在一个文件中。当token过期时,可以使用fetchAccessTokenWithRefreshToken()自动创建新的token-详情请参阅此处
根据您的需求,可能需要限制身份验证的能力。我们使用API来获取对系统电子邮件的访问权限,而不是允许网站通过页面用户发送邮件,因此:
// Get the token - redirect to the same page
if ( user_not_administrator ) {
    //TODO: redirect user to some error page and get yourself a notification
    exit;
}

$redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];

5 发送电子邮件!

function createMessage($sender, $to, $subject, $messageText) {
    $message = new Google_Service_Gmail_Message();

    $rawMessageString = "From: <{$sender}>\r\n";
    $rawMessageString .= "To: <{$to}>\r\n";
    $rawMessageString .= 'Subject: =?utf-8?B?' . base64_encode($subject) . "?=\r\n";
    $rawMessageString .= "MIME-Version: 1.0\r\n";
    $rawMessageString .= "Content-Type: text/html; charset=utf-8\r\n";
    $rawMessageString .= 'Content-Transfer-Encoding: quoted-printable' . "\r\n\r\n";
    $rawMessageString .= "{$messageText}\r\n";

    $rawMessage = strtr(base64_encode($rawMessageString), array('+' => '-', '/' => '_'));
    $message->setRaw($rawMessage);
    return $message;
}

function sendMessage($service, $userId, $message) {
    try {
        return $service->users_messages->send($userId, $message);
    } catch (Exception $e) {
        //todo error - use $e->getMessage();
    }
    return null;
}

sendMessage(new Google_Service_Gmail($client), 'me', createMessage(...));

Sources


此外,您可能会发现这些有趣的方法 $client->setLoginHint();$service = new Google_Service_Gmail($client);$me = $service->users->getProfile('me'); echo $me->emailAddress,以检查已进行身份验证的地址。注意:您需要拥有访问用户信息的特权 - 例如调用 getProfile() - Jiří

0

您需要使用OAUTH发送电子邮件。未来,每个谷歌应用程序都将使用oauth。

快速而简单的解决方案是打开不安全应用程序的访问权限。 默认情况下,Google会锁定大多数第三方客户端的IMAP访问权限,但您可以在此更改并继续发送电子邮件。

https://www.google.com/settings/security/lesssecureapps


2
这对于node应用程序来说有多危险?我如何切换节点应用程序以使用OATH? - SuperUberDuper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接