在使用smtp.Gmail.com:857的C#中使用Google的2步验证激活SmtpClient

时间:2016-02-04 14:05:52

标签: c# security authentication smtp gmail

我在使用C#中的Gmail SMTP服务器发送电子邮件时遇到问题。在StackOverflow中有很多关于这个问题的主题,但是那里的解决方案都没有使用我的特性。

我的具体情况是:

  • 最初生成应用专用密码,密码非常强
  • 由于某些其他应用而启用了双重验证(2步验证)和必需
  • 来自Google用户界面的
  • “允许安全应用较少”参数不可用

有人可以提出解决方案吗? 解决方案“禁用两步验证”不是一个有效的解决方案,我们正在寻找一个尊重主要帐户完整性的解决方案。

也许有使用Google的API或任何其他配置参数的解决方案?我怀疑是否有面向C#的解决方案,因为2步验证旨在提供更安全的帐户,但是不是“应用程序专用密码”可以绕过某些应用程序的2步验证吗?

我已生成特定的应用密码:https://security.google.com/settings/security/apppasswords

https://security.google.com/settings/security/apppasswords

Less Secure Apps参数不可用:https://www.google.com/settings/security/lesssecureapps

https://www.google.com/settings/security/lesssecureapps

这是我的代码:

public string sendEmailUsingGmail(string email)
    {
        using (MailMessage mail = new MailMessage("from_account@gmail.com", email)
        {
            Subject = "Aquaponey today",
            Body = "Hello, you are a Unicorn and you will be late for aquaponey today. - God",
            IsBodyHtml = true,
            Priority = System.Net.Mail.MailPriority.High
    })
        {
            using (SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", 587))
            {
                smtpClient.Credentials = new System.Net.NetworkCredential()
                {
                    UserName = "my_account@gmail.com",
                    Password = @"my_specific_app_password"
                };

                smtpClient.EnableSsl = true;
                System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate (object s,
                        System.Security.Cryptography.X509Certificates.X509Certificate certificate,
                        System.Security.Cryptography.X509Certificates.X509Chain chain,
                        System.Net.Security.SslPolicyErrors sslPolicyErrors)
                {
                    return true;
                };

                smtpClient.Send(mail);

                return "OK";
            }
        }
    }

我的客户返回的错误是:

  

System.Net.Mail.SmtpException:5.5.1需要身份验证。

了解更多信息

如果不可能,可能与How does google recognize a "trusted device" with 2-step verification

有关

1 个答案:

答案 0 :(得分:1)

我之前尝试使用Google API进行邮件发送。我的理解是你必须建立一个登录(这将触发两步),它为你提供一个开放的会话。然后通过该开放会话,您可以发送邮件。否则,很可能代码在等待登录的后半部分时超时。

更新:您可以使用服务器端身份验证过程,这将需要从最终用户进行一次性登录(必须通过双因素身份验证登录),这将为您提供刷新令牌可以用来重新连接到服务器。信息位于at the Google Developers Docs

摘录

  

实施服务器端授权

     

必须使用OAuth 2.0授权对Gmail API的请求   证书。您应该在应用程序时使用服务器端流程   需要代表用户访问Google API,例如   用户离线。这种方法需要通过一次性   从客户端到服务器的授权码;使用此代码   获取访问令牌并刷新服务器的令牌。

以下是该网站的示例python代码(我知道您正在使用C#)

将CLIENTSECRETS_LOCATION值替换为client_secrets.json文件的位置。

import logging
from oauth2client.client import flow_from_clientsecrets
from oauth2client.client import FlowExchangeError
from apiclient.discovery import build
# ...


# Path to client_secrets.json which should contain a JSON document such as:
#   {
#     "web": {
#       "client_id": "[[YOUR_CLIENT_ID]]",
#       "client_secret": "[[YOUR_CLIENT_SECRET]]",
#       "redirect_uris": [],
#       "auth_uri": "https://accounts.google.com/o/oauth2/auth",
#       "token_uri": "https://accounts.google.com/o/oauth2/token"
#     }
#   }
CLIENTSECRETS_LOCATION = '<PATH/TO/CLIENT_SECRETS.JSON>'
REDIRECT_URI = '<YOUR_REGISTERED_REDIRECT_URI>'
SCOPES = [
    'https://www.googleapis.com/auth/gmail.readonly',
    'https://www.googleapis.com/auth/userinfo.email',
    'https://www.googleapis.com/auth/userinfo.profile',
    # Add other requested scopes.
]

class GetCredentialsException(Exception):
  """Error raised when an error occurred while retrieving credentials.

  Attributes:
    authorization_url: Authorization URL to redirect the user to in order to
                       request offline access.
  """

  def __init__(self, authorization_url):
    """Construct a GetCredentialsException."""
    self.authorization_url = authorization_url


class CodeExchangeException(GetCredentialsException):
  """Error raised when a code exchange has failed."""


class NoRefreshTokenException(GetCredentialsException):
  """Error raised when no refresh token has been found."""


class NoUserIdException(Exception):
  """Error raised when no user ID could be retrieved."""


def get_stored_credentials(user_id):
  """Retrieved stored credentials for the provided user ID.

  Args:
    user_id: User's ID.
  Returns:
    Stored oauth2client.client.OAuth2Credentials if found, None otherwise.
  Raises:
    NotImplemented: This function has not been implemented.
  """
  # TODO: Implement this function to work with your database.
  #       To instantiate an OAuth2Credentials instance from a Json
  #       representation, use the oauth2client.client.Credentials.new_from_json
  #       class method.
  raise NotImplementedError()


def store_credentials(user_id, credentials):
  """Store OAuth 2.0 credentials in the application's database.

  This function stores the provided OAuth 2.0 credentials using the user ID as
  key.

  Args:
    user_id: User's ID.
    credentials: OAuth 2.0 credentials to store.
  Raises:
    NotImplemented: This function has not been implemented.
  """
  # TODO: Implement this function to work with your database.
  #       To retrieve a Json representation of the credentials instance, call the
  #       credentials.to_json() method.
  raise NotImplementedError()


def exchange_code(authorization_code):
  """Exchange an authorization code for OAuth 2.0 credentials.

  Args:
    authorization_code: Authorization code to exchange for OAuth 2.0
                        credentials.
  Returns:
    oauth2client.client.OAuth2Credentials instance.
  Raises:
    CodeExchangeException: an error occurred.
  """
  flow = flow_from_clientsecrets(CLIENTSECRETS_LOCATION, ' '.join(SCOPES))
  flow.redirect_uri = REDIRECT_URI
  try:
    credentials = flow.step2_exchange(authorization_code)
    return credentials
  except FlowExchangeError, error:
    logging.error('An error occurred: %s', error)
    raise CodeExchangeException(None)


def get_user_info(credentials):
  """Send a request to the UserInfo API to retrieve the user's information.

  Args:
    credentials: oauth2client.client.OAuth2Credentials instance to authorize the
                 request.
  Returns:
    User information as a dict.
  """
  user_info_service = build(
      serviceName='oauth2', version='v2',
      http=credentials.authorize(httplib2.Http()))
  user_info = None
  try:
    user_info = user_info_service.userinfo().get().execute()
  except errors.HttpError, e:
    logging.error('An error occurred: %s', e)
  if user_info and user_info.get('id'):
    return user_info
  else:
    raise NoUserIdException()


def get_authorization_url(email_address, state):
  """Retrieve the authorization URL.

  Args:
    email_address: User's e-mail address.
    state: State for the authorization URL.
  Returns:
    Authorization URL to redirect the user to.
  """
  flow = flow_from_clientsecrets(CLIENTSECRETS_LOCATION, ' '.join(SCOPES))
  flow.params['access_type'] = 'offline'
  flow.params['approval_prompt'] = 'force'
  flow.params['user_id'] = email_address
  flow.params['state'] = state
  return flow.step1_get_authorize_url(REDIRECT_URI)


def get_credentials(authorization_code, state):
  """Retrieve credentials using the provided authorization code.

  This function exchanges the authorization code for an access token and queries
  the UserInfo API to retrieve the user's e-mail address.
  If a refresh token has been retrieved along with an access token, it is stored
  in the application database using the user's e-mail address as key.
  If no refresh token has been retrieved, the function checks in the application
  database for one and returns it if found or raises a NoRefreshTokenException
  with the authorization URL to redirect the user to.

  Args:
    authorization_code: Authorization code to use to retrieve an access token.
    state: State to set to the authorization URL in case of error.
  Returns:
    oauth2client.client.OAuth2Credentials instance containing an access and
    refresh token.
  Raises:
    CodeExchangeError: Could not exchange the authorization code.
    NoRefreshTokenException: No refresh token could be retrieved from the
                             available sources.
  """
  email_address = ''
  try:
    credentials = exchange_code(authorization_code)
    user_info = get_user_info(credentials)
    email_address = user_info.get('email')
    user_id = user_info.get('id')
    if credentials.refresh_token is not None:
      store_credentials(user_id, credentials)
      return credentials
    else:
      credentials = get_stored_credentials(user_id)
      if credentials and credentials.refresh_token is not None:
        return credentials
  except CodeExchangeException, error:
    logging.error('An error occurred during code exchange.')
    # Drive apps should try to retrieve the user and credentials for the current
    # session.
    # If none is available, redirect the user to the authorization URL.
    error.authorization_url = get_authorization_url(email_address, state)
    raise error
  except NoUserIdException:
    logging.error('No user ID could be retrieved.')
  # No refresh token has been retrieved.
  authorization_url = get_authorization_url(email_address, state)
  raise NoRefreshTokenException(authorization_url)