如何在不启用“不安全访问"的情况下通过 gmail 发送电子邮件?
问题描述
Google 正在推动我们提高脚本访问其 gmail smtp 服务器的安全性.对于那件事我没有任何疑问.事实上,我很乐意提供帮助.
但他们并没有让事情变得容易.建议我们升级到使用最新安全措施的更安全的应用程序
很好,但这并不能帮助我弄清楚如何升级看起来像这样的代码:
server = smtplib.SMTP("smtp.gmail.com", 587)服务器.ehlo()server.starttls()server.login(GMAIL_USER, GMAIL_PASSWORD)server.sendmail(发件人,收件人,邮件)server.close()
当然,我会打开访问安全性较低的应用程序",但如果有人想出用什么替换此代码,我将不胜感激.
解决方案以下是 Python 3 和 GMail 当前 API 的更新示例.
请注意,要获取下面的 credentials.json
文件,您需要创建一个 Oauth 客户端 ID 凭据
导入base64导入日志导入 mimetypes导入操作系统导入 os.path进口泡菜从 email.mime.text 导入 MIMEText从 google_auth_oauthlib.flow 导入 InstalledAppFlow从 google.auth.transport.requests 导入请求从 googleapiclient 导入错误从 googleapiclient.discovery 导入构建定义 get_service():"""获取授权的 Gmail API 服务实例.回报:授权的 Gmail API 服务实例.."""# 如果修改这些范围,删除文件 token.pickle.范围 = ['https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/gmail.send',]信用=无# 文件 token.pickle 存储用户的访问和刷新令牌,并且是# 当授权流程第一次完成时自动创建# 时间.如果 os.path.exists('token.pickle'):以 open('token.pickle', 'rb') 作为令牌:信用=pickle.load(令牌)# 如果没有(有效)凭据可用,让用户登录.如果不是 creds 或 not creds.valid:如果 creds 和 creds.expired 和 creds.refresh_token:creds.refresh(请求())别的:flow = InstalledAppFlow.from_client_secrets_file('credentials.json',范围)creds = flow.run_local_server(port=0)# 保存下次运行的凭据以 open('token.pickle', 'wb') 作为令牌:pickle.dump(信用,令牌)服务=构建('gmail','v1',凭据=信用)退货服务def send_message(服务,发件人,消息):"""发送电子邮件.参数:service:授权的 Gmail API 服务实例.user_id:用户的电子邮件地址.我"的特殊价值可用于指示经过身份验证的用户.消息:要发送的消息.回报:已发送消息."""尝试:sent_message = (service.users().messages().send(userId=sender, body=message).执行())logging.info('消息 ID: %s', sent_message['id'])返回已发送的消息除了errors.HttpError作为错误:logging.error('发生 HTTP 错误: %s', 错误)def create_message(发件人,收件人,主题,message_text):"""为电子邮件创建消息.参数:发件人:发件人的电子邮件地址.to:收件人的电子邮件地址.主题:电子邮件的主题.message_text:电子邮件的文本.回报:一个包含 base64url 编码的电子邮件对象的对象."""消息 = MIMEText(消息文本)消息['to'] = to消息['来自'] = 发件人消息['主题'] = 主题s = message.as_string()b = base64.urlsafe_b64encode(s.encode('utf-8'))返回 {'raw': b.decode('utf-8')}如果 __name__ == '__main__':logging.basicConfig(格式="[%(levelname)s] %(message)s",级别=日志记录.INFO)尝试:服务 = 获取服务()message = create_message("from@gmail.com", "to@gmail.com", "Test subject", "Test body")send_message(服务,来自@gmail.com",消息)例外为 e:logging.error(e)增加
Google are pushing us to improve the security of script access to their gmail smtp servers. I have no problem with that. In fact I'm happy to help.
But they're not making it easy. It's all well and good to suggest we Upgrade to a more secure app that uses the most up to date security measures
, but that doesn't help me work out how to upgrade bits of code that look like this:
server = smtplib.SMTP("smtp.gmail.com", 587)
server.ehlo()
server.starttls()
server.login(GMAIL_USER, GMAIL_PASSWORD)
server.sendmail(FROM, TO, MESSAGE)
server.close()
Sure, I'll go and turn on "Access for less secure apps", but if anyone has worked out what to replace this code with, I'll be grateful.
解决方案Updated sample for Python 3, and GMail's current API, below.
Note that to get the credentials.json
file below, you'll need to create an Oauth client ID credential here, after selecting the relevant GCP project. Once you've created it you'll be shown the client key and client secret. Close that prompt, and click the down arrow next to the account. This is the file you'll need.
import base64
import logging
import mimetypes
import os
import os.path
import pickle
from email.mime.text import MIMEText
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient import errors
from googleapiclient.discovery import build
def get_service():
"""Gets an authorized Gmail API service instance.
Returns:
An authorized Gmail API service instance..
"""
# If modifying these scopes, delete the file token.pickle.
SCOPES = [
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.send',
]
creds = None
# The file token.pickle 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('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# 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.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('gmail', 'v1', credentials=creds)
return service
def send_message(service, sender, message):
"""Send an email message.
Args:
service: Authorized Gmail API service instance.
user_id: User's email address. The special value "me"
can be used to indicate the authenticated user.
message: Message to be sent.
Returns:
Sent Message.
"""
try:
sent_message = (service.users().messages().send(userId=sender, body=message)
.execute())
logging.info('Message Id: %s', sent_message['id'])
return sent_message
except errors.HttpError as error:
logging.error('An HTTP error occurred: %s', error)
def create_message(sender, to, subject, message_text):
"""Create a message for an email.
Args:
sender: Email address of the sender.
to: Email address of the receiver.
subject: The subject of the email message.
message_text: The text of the email message.
Returns:
An object containing a base64url encoded email object.
"""
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
s = message.as_string()
b = base64.urlsafe_b64encode(s.encode('utf-8'))
return {'raw': b.decode('utf-8')}
if __name__ == '__main__':
logging.basicConfig(
format="[%(levelname)s] %(message)s",
level=logging.INFO
)
try:
service = get_service()
message = create_message("from@gmail.com", "to@gmail.com", "Test subject", "Test body")
send_message(service, "from@gmail.com", message)
except Exception as e:
logging.error(e)
raise
相关文章