提问人:Booboo 提问时间:11/15/2023 最后编辑:Linda Lawton - DaImToBooboo 更新时间:11/15/2023 访问量:37
使用 SMTP 使用我的 Gmail 帐户发送电子邮件的范围XOAUTH2范围混淆
Scopes confusion using SMTP to send email using my Gmail account with XOAUTH2
问:
我的应用程序有一个现有模块,我用于发送电子邮件,该模块访问 SMTP 服务器并使用用户(电子邮件地址)和密码进行授权。现在我正在尝试使用Gmail使用我的Gmail帐户做同样的事情,为了论证,我们说是[email protected](实际上是不同的东西)。
首先,我创建了一个 Gmail 应用程序。在有点令人困惑的同意屏幕上,我开始添加“敏感”或“受限”的范围。如果我想让应用程序“生产”,我被告知它必须经过验证过程,并且我必须提供某些文档。这不适合我,因为我,这个帐户的所有者,只是为了以编程方式发送电子邮件而尝试连接到它。我他们为桌面应用程序创建了凭据并将其下载到文件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')
浏览器窗口打开,说这不是一个经过验证的应用程序,我有机会“回到安全”,但我点击了高级链接并最终获得了我的令牌。
然后,我尝试使用以下代码发送电子邮件:
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"] = '[email protected]'
message["To"] = '[email protected]'
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', ['[email protected]'], message.as_string())
这可行,但请注意,我使用了不同的作用域 https://www.googleapis.com/auth/gmail.send 而不是用于获取初始访问令牌的 https://mail.google.com/。
然后,我编辑了应用程序以添加范围 https://www.googleapis.com/auth/gmail.send。这需要我将应用程序置于测试模式。我不理解添加“测试用户”的部分,也就是说,我不知道我可以/应该在这里输入什么。然后,我生成了新的凭据和新的令牌,如上所述。然后,当我发送电子邮件时,我看到(调试已打开):
...
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授权字符串。我不知道这是否是因为我没有添加测试用户而发生的。就其价值而言,我不相信这个新令牌已经过期,因此它没有刷新。
我没有尝试过,但是如果我将应用程序设置为“生产”,它会起作用吗?同样,我不想使用Gmail进行整个验证过程。不幸的是,除了我想定义一个范围更受限制的应用程序并使用它之外,我没有一个具体的问题,但如果不经过此验证,这似乎是不可能的。有什么建议吗?
答:
好的,首先,这将是一个单用户应用程序。作为开发人员,您将是唯一使用它的人,而您只是以编程方式发送电子邮件,就可以开始澄清一些事情。
- 您无需验证此应用程序。是的,您只需要像以前一样通过该屏幕。不用担心。
not a verified application
- 通过单击按钮在生产环境中设置应用程序。将使你能够请求不会过期的刷新令牌。你想要这个。同样,请忽略显示您需要验证不需要的应用的屏幕。
send to production
- 测试用户,只要仅使用创建项目的用户登录,就不需要测试用户。忽略它。
- 它只是您使用应用程序使用范围
https://mail.google.com/
- 不要担心将范围添加到 google cloud 项目,它只是为了验证。重要的是代码中的内容。
好了,一切都清楚了。
您现在有两个选择。
- XOauth2,这就是你现在正在做的事情。
- 应用程序密码。只需在您的Google帐户上创建一个应用程序密码,然后使用它来代替您的实际Google密码,您现有的代码就可以了。我如何使用 Python 发送电子邮件。在2.2分钟内平坦!
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 = "[email protected]"
subject = "Test email Oauth2"
msg = "Hello world"
sender = user
recipients = [user]
send_email(host, port, subject, msg, sender, recipients)
if __name__ == '__main__':
main()
评论
InstalledAppFlow
评论