我的应用程序有一个现有的模块,用于发送访问SMTP服务器并使用用户(邮箱地址)和密码进行授权的邮箱.现在我正试着用我的Gmail账户来做同样的事情,为了不引起争论,我们说它是boboo@gmail.com(它实际上是不同的).

首先,我创建了一个Gmail应用程序.在同意屏幕上,我开始添加"敏感"或"受限"的作用域,这有点令人困惑.如果我想让应用程序"生产",我被告知它必须经过验证过程,我必须生成某些文档.这不适合我,因为我作为这个帐户的所有者,只是为了通过编程发送邮箱而try 连接到它.我为一个桌面应用程序创建了凭据,并将其下载到文件credentials.json.

接下来,我使用以下代码获取了一个访问令牌:

from google_auth_oauthlib.flow import InstalledAppFlow

SCOPES = ['https://mail.google.com/']

def get_initial_credentials(*, token_path, credentials_path):
        flow = InstalledAppFlow.from_client_secrets_file(credentials_path, SCOPES)
        creds = flow.run_local_server(port=0)

        with open(token_path, 'w') as f:
            f.write(creds.to_json())

if __name__ == '__main__':
    get_initial_credentials(token_path='token.json', credentials_path='credentials.json')

一个浏览器窗口打开,显示这不是一个经过验证的应用程序,我有机会"返回到安全地带",但我点击了"高级"链接,最终拿到了我的令牌.

然后,我try 发送包含以下代码的邮箱:

import smtplib
from email.mime.text import MIMEText

import base64
import json

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow

SCOPES = ['https://www.googleapis.com/auth/gmail.send']

def get_credentials(token_path):

    with open(token_path) as f:
        creds = Credentials.from_authorized_user_info(json.load(f), SCOPES)

    if not creds.valid:
        creds.refresh(Request())
        with open(token_path, 'w') as f:
            f.write(creds.to_json())

    return creds


def generate_OAuth2_string(access_token):
    auth_string = f'user=booboo\1auth=Bearer {access_token}\1\1'
    return base64.b64encode(auth_string.encode('utf-8')).decode('ascii')

message = MIMEText('I need lots of help!', "plain")
message["From"] = 'booboo@gmail.com'
message["To"] = 'booboo@gmail.com'
message["Subject"] = 'Help needed with Gmail'

creds = get_credentials('token.json')
xoauth_string = generate_OAuth2_string(creds.token)

with smtplib.SMTP('smtp.gmail.com', 587) as conn:
    conn.starttls()
    conn.docmd('AUTH', 'XOAUTH2 ' + xoauth_string)
    conn.sendmail('booboo', ['booboo@gmail.com'], message.as_string())

这是可行的,但请注意,我使用了不同的作用域100,而不是我用来获取初始访问令牌的101.

然后,我编辑了应用程序以添加作用域100.这要求我将应用程序置于测试模式.我不理解添加"测试用户"的部分,也就是说我不知道我可以/应该在这里输入什么.然后,我如上所述生成了新的凭据和新的令牌.然后,当我发送邮箱时,我看到(调试打开):

...
reply: b'535-5.7.8 Username and Password not accepted. Learn more at\r\n'
reply: b'535 5.7.8  https://support.google.com/mail/?p=BadCredentials l19-20020ac84a93000000b0041b016faf7esm2950068qtq.58 - gsmtp\r\n'
reply: retcode (535); Msg: b'5.7.8 Username and Password not accepted. Learn more at\n5.7.8  https://support.google.com/mail/?p=BadCredentials l19-20020ac84a93000000b0041b016faf7esm2950068qtq.58 - gsmtp'
...

但我从未发送过密码,而是发送了XOAUTH2授权字符串.我不知道这是否因为我没有添加测试用户而发生.无论如何,我不相信这个新的令牌已经过期,因此它没有被刷新.

我没有try 过,但如果我将应用程序设置为"Products",它会工作吗?再说一次,我不想用Gmail经历一个完整的验证过程.不幸的是,除了我想定义一个范围更受限制的应用程序并使用它之外,我没有其他特定的问题,但如果不经过这个验证,这似乎是不可能的.有什么建议吗?

推荐答案

好吧,首先,因为这将是一个单一用户的应用程序.你的开发人员将是唯一一个使用它,你只是发送邮箱programmiclly让清除一些事情开始.

  1. 您不需要验证此应用程序.是的,你需要像你所做的那样通过not a verified application个屏幕.别担心.
  2. 通过单击send to production按钮在生产中设置应用程序.将使您能够请求不会过期的刷新令牌.你想要这个.同样,忽略显示您将需要验证您的应用程序的屏幕.
  3. 测试用户,只要您只使用您创建项目的用户登录,就不需要测试用户.别理它.
  4. 这只是你使用的应用程序使用https://mail.google.com/范围
  5. 不要担心将作用域添加到Google云项目中,这只是为了验证.重要的是代码中的内容.

好的,都清楚了.

您现在有两个 Select .

  1. XOAuth2,这就是您现在正在做的.
  2. 应用程序密码.只需在你的谷歌账户上创建一个应用程序密码,然后用它来取代你的实际谷歌密码,你现有的代码就可以工作了.How I send emails with Python. In 2.2 minutes flat!

Xoauth2

如果您想使用XOauth2,那么您可以使用Google API Python客户端库来帮助您获取所需的授权令牌

#   To install the Google client library for Python, run the following command:
#   pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib


from __future__ import print_function

import base64
import os.path
import smtplib
from email.mime.text import MIMEText

import google.auth.exceptions
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.errors import HttpError

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://mail.google.com/']

# usr token storage

USER_TOKENS = 'token.json'

# application credentials

CREDENTIALS = 'C:\YouTube\dev\credentials.json'


def getToken() -> str:
    """ Gets a valid Google access token with the mail scope permissions. """

    creds = None

    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists(USER_TOKENS):
        try:
            creds = Credentials.from_authorized_user_file(USER_TOKENS, SCOPES)
            creds.refresh(Request())
        except google.auth.exceptions.RefreshError as error:
            # if refresh token fails, reset creds to none.
            creds = None
            print(f'An error occurred: {error}')
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                CREDENTIALS, SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open(USER_TOKENS, 'w') as token:
            token.write(creds.to_json())

    try:

        return creds.token
    except HttpError as error:
        # TODO(developer) - Handle errors from authorization request.
        print(f'An error occurred: {error}')


def generate_oauth2_string(username, access_token, as_base64=False) -> str:

    # creating the authorization string needed by the auth server.
    #auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token)

    auth_string = 'user=' + username + '\1auth=Bearer ' + access_token + '\1\1'
    if as_base64:
        auth_string = base64.b64encode(auth_string.encode('ascii')).decode('ascii')
    return auth_string


def send_email(host, port, subject, msg, sender, recipients):
    access_token = getToken()
    auth_string = generate_oauth2_string(sender, access_token, as_base64=True)

    msg = MIMEText(msg)
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = ', '.join(recipients)

    server = smtplib.SMTP(host, port)
    server.starttls()
    server.docmd('AUTH', 'XOAUTH2 ' + auth_string)
    server.sendmail(sender, recipients, msg.as_string())
    server.quit()


def main():
    host = "smtp.gmail.com"
    port = 587

    user = "test@gmail.com"

    subject = "Test email Oauth2"
    msg = "Hello world"
    sender = user
    recipients = [user]
    send_email(host, port, subject, msg, sender, recipients)


if __name__ == '__main__':
    main()

Python相关问答推荐

有什么方法可以避免使用许多if陈述

如何使用没有Selenium的Python在百思买着陆页面上处理国家/地区 Select ?

将DF中的名称与另一DF拆分并匹配并返回匹配的公司

我如何使法国在 map 中完全透明的代码?

如何在Python脚本中附加一个Google tab(已经打开)

如何在Raspberry Pi上检测USB并使用Python访问它?

Godot:需要碰撞的对象的AdditionerBody2D或Area2D以及queue_free?

NumPy中条件嵌套for循环的向量化

移动条情节旁边的半小提琴情节在海运

如何使用Pandas DataFrame按日期和项目汇总计数作为列标题

启用/禁用shiny 的自动重新加载

将scipy. sparse矩阵直接保存为常规txt文件

如何防止Pandas将索引标为周期?

为什么常规操作不以其就地对应操作为基础?

从列表中获取n个元素,其中list [i][0]== value''

并行编程:同步进程

处理Gekko的非最优解

我对这个简单的异步者的例子有什么错误的理解吗?

在我融化极点数据帧之后,我如何在不添加索引的情况下将其旋转回其原始形式?

如何防止html代码出现在quarto gfm报告中的pandas表之上