繁体   English   中英

如何在不启用“不安全访问”的情况下通过 gmail 发送电子邮件?

[英]How to send an email through gmail without enabling 'insecure access'?

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()

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

这很痛苦,但我现在似乎有些事情......

Python3不支持(尚未)

我认为这不会太难实现,因为我在转换软件包时遇到了磕磕绊绊而没有发生任何大规模的事情:只是通常的2to3。 然而,经过几个小时,我厌倦了向上游游泳。 在撰写本文时,我找不到已发布的用于Python 3公共消费的软件包.python 2体验很简单(相比之下)。

导航谷歌网站是成功的一半

毫无疑问,随着时间的推移,这将改变。 最终,您需要下载client_secret.json文件。 你只能(可能)通过网络浏览器进行设置:

  1. 你需要一个谷歌帐户 - 谷歌应用程序或Gmail。 所以,如果你还没有,那么去买一个。
  2. 开始使用开发人员控制台
  3. 创建一个新项目,然后等待4或400秒才能完成。
  4. 导航到API's and Auth - > Credentials
  5. OAuth选择“ Create New Client ID
  6. 选择Installed Application作为应用程序类型和Other
  7. 你现在应该有一个按钮Download JSON 去做。 这是你的client_secret.json - 可以这么说的密码

但等等不是全部!

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

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

新闻快报! 哇。 现在还有更多!

  1. 导航到API的&auth - > API - > Gmail API
  2. 单击“启用API”按钮

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

Python 2

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

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

首先你需要一些库:

pip install --upgrade google-api-python-client
pip install --upgrade python-gflags
  • 你需要改变地址和地址
  • 确保您拥有Storage指令所期望的client_token.json文件
  • 该目录需要是可写的,因此它可以保存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)

希望这让我们全部开始。 不像旧方式那么简单,但看起来复杂得多,现在我可以在肉体中看到它。

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”)并刷新您的程序。 在这篇文章中可以看到更多细节。

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

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

我包含了一些已针对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()

下面是Python 3和GMail当前API的更新示例。

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

在此输入图像描述

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

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

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

希望这有帮助👍

  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;
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM