【问题标题】:How to send an email through gmail without enabling 'insecure access'?如何在不启用“不安全访问”的情况下通过 gmail 发送电子邮件?
【发布时间】:2014-09-20 03:24:58
【问题描述】:

Google 正在推动我们提高脚本访问其 gmail smtp 服务器的安全性。对于那件事我没有任何疑问。事实上,我很乐意提供帮助。

但他们并没有让事情变得容易。建议我们Upgrade to a more secure app that uses the most up to date security measures 很好,但这并不能帮助我弄清楚如何升级如下所示的代码:

server = smtplib.SMTP("smtp.gmail.com", 587)
server.ehlo()
server.starttls()
server.login(GMAIL_USER, GMAIL_PASSWORD)
server.sendmail(FROM, TO, MESSAGE)
server.close()

当然,我会打开“访问安全性较低的应用程序”,但如果有人想出用什么替换此代码,我将不胜感激。

【问题讨论】:

  • @AndréDaniel 所以“不太安全”的语言主要面向使用第三方程序的人——在这些情况下,用户需要向第三方程序提供他们的 Gmail 密码(因为 SMTP 不'不支持 OAuth),这被认为不太安全。这就是为什么该设置被标记为它是什么。
  • @Amber 他们可以生成唯一的应用密码。这将解决安全问题,而无需迁移到完全不同的 API。

标签: python gmail


【解决方案1】:

这很痛苦,但我现在似乎有什么事情发生了......

不支持 Python3(目前)

我认为这不会太难实现,因为我在转换包时磕磕绊绊,没有遇到任何大问题:只是通常的 2to3 东西。然而,几个小时后,我厌倦了逆流而上。在撰写本文时,我找不到公开的 Python 3 软件包。python 2 的体验是直截了当的(相比之下)。

浏览 Google 网站是成功的一半

毫无疑问,随着时间的推移,这种情况会发生变化。最终你需要下载一个client_secret.json 文件。您只能(可能)通过网络浏览器进行此设置:

  1. 您需要一个 google 帐户 - google 应用程序或 gmail。所以,如果你还没有,那就去买一个吧。
  2. 联系developers console
  3. 创建一个新项目,然后等待 4 或 400 秒以完成。
  4. 导航到API's and Auth -> Credentials
  5. OAuth 下选择Create New Client ID
  6. 选择Installed Application作为应用程序类型和其他
  7. 您现在应该有一个按钮Download JSON。去做。这是你的client_secret.json——可以说是密码

但等等,这还不是全部!

您必须为您的应用程序提供一个“产品名称”以避免出现一些奇怪的错误。 (看看我受了多少苦才给你这个 ;-)

  1. 导航到API's & auth -> Consent Screen
  2. 选择您的电子邮件
  3. 输入产品名称。它是什么并不重要。 “Foobar”会很好。
  4. 保存

快讯!哇。现在还有更多!

  1. 导航到 API 和身份验证 -> API -> Gmail API
  2. 点击按钮启用 API

是的。现在我们可以更新电子邮件脚本了。

Python 2

您需要第一次以交互方式运行脚本。它将在您的机器上打开一个网络浏览器,您将授予权限(点击一个按钮)。本练习将在您的计算机上保存一个文件gmail.storage,其中包含一个可重复使用的令牌。

[我没有运气将令牌转移到没有图形浏览器功能的机器上——返回一个 HTTPError。我试图通过 lynx 图形浏览器来解决它。那也失败了,因为谷歌已将最终的“接受”按钮设置为“禁用”!?我会提出另一个问题来跳过这个障碍(更多抱怨)]

首先你需要一些库:

pip install --upgrade google-api-python-client
pip install --upgrade python-gflags
  • 您需要更改收件地址和发件人地址
  • 确保您拥有 client_token.json 文件,无论 Storage 指令所期望的位置
  • 目录需要是可写的,这样才能保存gmail.storage文件

最后是一些代码:

import base64
import httplib2

from email.mime.text import MIMEText

from apiclient.discovery import build
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import run


# Path to the client_secret.json file downloaded from the Developer Console
CLIENT_SECRET_FILE = 'client_secret.json'

# Check https://developers.google.com/gmail/api/auth/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/gmail.compose'

# Location of the credentials storage file
STORAGE = Storage('gmail.storage')

# Start the OAuth flow to retrieve credentials
flow = flow_from_clientsecrets(CLIENT_SECRET_FILE, scope=OAUTH_SCOPE)
http = httplib2.Http()

# Try to retrieve credentials from storage or run the flow to generate them
credentials = STORAGE.get()
if credentials is None or credentials.invalid:
  credentials = run(flow, STORAGE, http=http)

# Authorize the httplib2.Http object with our credentials
http = credentials.authorize(http)

# Build the Gmail service from discovery
gmail_service = build('gmail', 'v1', http=http)

# create a message to send
message = MIMEText("Message goes here.")
message['to'] = "yourvictim@goes.here"
message['from'] = "you@go.here"
message['subject'] = "your subject goes here"
body = {'raw': base64.b64encode(message.as_string())}

# send it
try:
  message = (gmail_service.users().messages().send(userId="me", body=body).execute())
  print('Message Id: %s' % message['id'])
  print(message)
except Exception as error:
  print('An error occurred: %s' % error)

希望这能让我们都开始。不像旧方法那么简单,但现在我可以亲眼看到它确实看起来不那么复杂。

【讨论】:

  • 这对我很有用,非常好的和详细的答案:)
  • +1 用粗体表示浏览 google 的文档是成功的一半。在看到你的答案之前,我几乎要踢球并尝试其他东西。谢谢@John Mee
  • 谢谢,很有帮助!但需要将正文行改为“body = { 'raw': base64.b64encode(message.as_string()).replace('+', '-').replace('/', '_') }” stackoverflow.com/a/27468015
  • @user2426679 urllib.quote_plus 是否做同样的工作(只是更好)?
  • @john mee:不,谷歌有自己深奥的特质
【解决方案2】:

John Mee 的回答似乎已经过时了。 它在 2016 年 7 月不起作用。 可能是由于 Gmail 的 API 更新所致。 我将他的代码(python 2)更新如下:

    """Send an email message from the user's account.
"""

import base64
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import mimetypes
import os

#from __future__ import print_function
import httplib2
import os

from apiclient import discovery
import oauth2client
from oauth2client import client
from oauth2client import tools

from apiclient import errors

SCOPES = 'https://www.googleapis.com/auth/gmail.compose'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Gmail API Python Quickstart'

try:
    import argparse
    flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
    flags = None

def SendMessage(service, user_id, 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:
    message = (service.users().messages().send(userId=user_id, body=message)
               .execute())
    print 'Message Id: %s' % message['id']
    return message
  except errors.HttpError, error:
    print 'An error occurred: %s' % error


def CreateMessage(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
  return {'raw': base64.urlsafe_b64encode(message.as_string())}


def get_credentials():
    """Gets valid user credentials from storage.

    If nothing has been stored, or if the stored credentials are invalid,
    the OAuth2 flow is completed to obtain the new credentials.

    Returns:
        Credentials, the obtained credential.
    """
    home_dir = os.path.expanduser('~')
    credential_dir = os.path.join(home_dir, '.credentials')
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)
    credential_path = os.path.join(credential_dir,
                                   'sendEmail.json')

    store = oauth2client.file.Storage(credential_path)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        if flags:
            credentials = tools.run_flow(flow, store, flags)
        else: # Needed only for compatibility with Python 2.6
            credentials = tools.run(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials

if __name__ == "__main__":

    try:
        credentials = get_credentials()
        http = credentials.authorize(httplib2.Http())
        service = discovery.build('gmail', 'v1', http=http)
        SendMessage(service, "me", CreateMessage("send@gmail.com", "receive@gmail.com", "Test gmail automation", "Hello world"))

    except Exception, e:
        print e
        raise

请注意,如果您遇到错误Insufficient Permission,一个可能的原因是程序中的范围设置不正确。 另一个可能的原因可能是您需要删除存储 json 文件(本程序中的“sendEmail.json”)并刷新您的程序。更多细节可以在这个post看到。

【讨论】:

  • 无论如何要让这个新方法在 python 3.3 中工作吗?我正在使用旧的 SMTP 登录方法的新服务器上收到 SMTPAuthenticationError,但我所有的旧服务器仍在工作。允许不太安全的应用程序已打开。我也做了 DisplayUnlockCaptcha 步骤。
  • @ccy 您如何通过内置身份验证重置 django 密码以使用这种发送电子邮件的方法?
  • @jonnyd42 抱歉,我没有使用 django 发送此电子邮件,因此不知道该怎么做。您可以创建一个新问题。祝你好运,
【解决方案3】:

Python 3 的更新示例和 GMail 的当前 API,如下所示。

请注意,要获取下面的credentials.json 文件,您需要在选择相关的 GCP 项目后创建一个 Oauth 客户端 ID 凭据 here。创建后,您将看到客户端密钥和客户端密码。关闭该提示,然后单击帐户旁边的向下箭头。这是您需要的文件。

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

【讨论】:

  • 非常有帮助,谢谢!这在 2021 年 9 月仍然有效。
【解决方案4】:

您是否考虑过使用 Gmail API?该 API 具有内置的安全功能,并专门针对 Gmail 进行了优化。您可以在 http://developers.google.com 上找到 API 文档 - 例如,这里是 Send API 调用的文档:

https://developers.google.com/gmail/api/v1/reference/users/messages/send

【讨论】:

【解决方案5】:

我包含了一些已针对 python 3 使用更新的代码 - 一旦您获得必要的权限和 OAuth 令牌工作,它似乎会发送电子邮件。它主要基于 Google API 网站示例。

from __future__ import print_function

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

import httplib2
from apiclient import discovery
from googleapiclient import errors
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage

try:
    import argparse

    flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
    flags = None

# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/gmail-python-quickstart.json
SCOPES = 'https://www.googleapis.com/auth/gmail.send'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Gmail API Python Quickstart'


def get_credentials():
    """Gets valid user credentials from storage.

    If nothing has been stored, or if the stored credentials are invalid,
    the OAuth2 flow is completed to obtain the new credentials.

    Returns:
        Credentials, the obtained credential.
    """
    home_dir = os.path.expanduser('~')
    credential_dir = os.path.join(home_dir, '.credentials')
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)
    credential_path = os.path.join(credential_dir,
                                   'gmail-python-quickstart.json')

    store = Storage(credential_path)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        if flags:
            credentials = tools.run_flow(flow, store, flags)
        else:  # Needed only for compatibility with Python 2.6
            credentials = tools.run(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials


to = 'test@email.com'
sender = 'test@email.com'
subject = 'test emails'
message_text = 'hello this is a text test message'
user_id = 'me'

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
    return {'raw': (base64.urlsafe_b64encode(message.as_bytes()).decode())}


def send_message(service, user_id, 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:
        message = (service.users().messages().send(userId=user_id, body=message)
                   .execute())
        print('Message Id: {}'.format(message['id']))
        return message
    except errors.HttpError as error:
        print('An error occurred: {}'.format(error))


def main():
    """Shows basic usage of the Gmail API.

    Creates a Gmail API service object and outputs a list of label names
    of the user's Gmail account.
    """
    credentials = get_credentials()
    http = credentials.authorize(httplib2.Http())
    service = discovery.build('gmail', 'v1', http=http)

    msg = create_message(sender,to,subject,message_text)
    message = (service.users().messages().send(userId=user_id, body=msg)
               .execute())
    print('Message Id: {}'.format(message['id']))
    results = service.users().messages().list(userId='me').execute()
    labels = results.get('labels', [])

    if not labels:
        print('No labels found.')
    else:
        print('Labels:')
        for label in labels:
            print(label['name'])


if __name__ == '__main__':
    main()

【讨论】:

    【解决方案6】:

    这很简单。 转到 Gmail 帐户设置。 搜索“应用密码”。 生成应用密码。 使用该密码而不是 Gmail 密码。 应用密码的优点是您不需要关闭两因素身份验证,也不需要允许不安全的应用。

    这是关于谷歌应用密码的官方链接https://support.google.com/mail/answer/185833?hl=en-GB

    希望对你有帮助?

    【讨论】:

      【解决方案7】:
        protected string SendEmail(string toAddress, string subject, string body)
          {
              string result = "Message Sent Successfully..!!";
      
              string senderID = "...........";// use sender's email id here..
              const string senderPassword = "........."; // sender password here...
      
              try
              {
                  SmtpClient smtp = new SmtpClient
                  {
                      Host = "smtp.gmail.com", // smtp server address here...
                      Port = 587,
                      EnableSsl = true,
                      DeliveryMethod = SmtpDeliveryMethod.Network,
                      Credentials = new System.Net.NetworkCredential(senderID, senderPassword),
                      Timeout = 30000,
      
                  };
      
                  MailMessage message = new MailMessage(senderID, toAddress, subject, body);
      
                  smtp.Send(message);
              }
              catch (Exception ex)
              {
                  result = "Error sending email.!!!";
              }
      
              return result;
          }
      

      【讨论】:

      • 谢谢pinkey,但我们这里说的是python。
      猜你喜欢
      • 2018-10-08
      • 1970-01-01
      • 1970-01-01
      • 2017-09-10
      • 2015-10-29
      • 2020-05-30
      • 2021-06-24
      • 2012-01-07
      相关资源
      最近更新 更多