实现服务器端授权

向 Gmail API 发出的请求必须使用 OAuth 2.0 凭据进行授权。当您的应用需要代表用户访问 Google API 时(例如,当用户处于离线状态时),您应该使用服务器端流程。此方法需要将一次性授权代码从客户端传递到服务器;此代码用于获取服务器的访问令牌和刷新令牌。

如需详细了解服务器端 Google OAuth 2.0 实现,请参阅针对网络服务器应用使用 OAuth 2.0

目录

创建客户端 ID 和客户端密钥

要开始使用 Gmail API,您需要先使用设置工具,该工具会引导您在 Google API 控制台中创建项目、启用 Gmail API 以及创建凭据。

  1. 在“凭据”页面上,请依次点击创建凭据 > OAuth 客户端 ID 创建 OAuth 2.0 凭据,或依次点击创建凭据 > 服务账号密钥创建服务账号。
  2. 如果创建的是 OAuth 客户端 ID,请选择应用类型。
  3. 填写表单并点击创建

“凭据”页面现在会列出应用的客户端 ID 和服务账号密钥。您可以点击客户端 ID 查看详情,其中的具体参数会因 ID 类型而异,但可能包含电子邮件地址、客户端密钥、JavaScript 来源和重定向 URI。

记下客户端 ID,因为稍后您需要将其添加到代码中。

处理授权请求

当用户首次加载您的应用时,系统会显示一个对话框,以授权您的应用在所请求的权限范围内访问其 Gmail 帐号。完成此初始授权后,只有当应用的客户端 ID 或请求的范围发生更改时,系统才会向用户显示权限对话框。

对用户进行身份验证

首次登录会返回一个授权结果对象,如果成功,则其中包含一个授权代码

使用授权代码交换访问令牌

授权代码是一个一次性代码,您的服务器可以使用该代码换取一个访问令牌。此访问令牌将传递给 Gmail API,以向您的应用授予对用户数据的限时访问权限。

如果您的应用需要 offline 访问权限,则在其首次交换授权代码时,还会收到一个刷新令牌,并在前一个令牌过期后使用该令牌接收新的访问令牌。您的应用会存储此刷新令牌(通常存储在您服务器的数据库中),以供日后使用。

以下代码示例演示了如何用授权代码交换具有 offline 访问权限的访问令牌,以及如何存储刷新令牌。

Python

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)

使用存储的凭据进行授权

如果用户在首次授权流程成功后访问您的应用时,您的应用可以使用存储的刷新令牌对请求进行授权,而无需再次提示用户。

如果您已对用户进行身份验证,您的应用可以从其数据库中检索刷新令牌,并将该令牌存储在服务器端会话中。如果刷新令牌被撤消或因其他原因而无效,您需要捕获此情况并采取适当的措施。

使用 OAuth 2.0 凭据

如上一部分所示,获取 OAuth 2.0 凭据后,便可使用这些凭据授权 Gmail 服务对象并向 API 发送请求。

实例化服务对象

此代码示例展示了如何实例化服务对象,然后授权其发出 API 请求。

Python

from apiclient.discovery import build
# ...

def build_service(credentials):
  """Build a Gmail service object.

  Args:
    credentials: OAuth 2.0 credentials.

  Returns:
    Gmail service object.
  """
  http = httplib2.Http()
  http = credentials.authorize(http)
  return build('gmail', 'v1', http=http)

发送已获授权的请求并检查是否存在已撤消的凭据

以下代码段使用已获授权的 Gmail 服务实例来检索邮件列表。

如果发生错误,代码会检查 HTTP 401 状态代码,应将用户重定向到授权网址来处理该代码。

API 参考文档中介绍了更多 Gmail API 操作。

Python

from apiclient import errors
# ...

def ListMessages(service, user, query=''):
  """Gets a list of messages.

  Args:
    service: Authorized Gmail API service instance.
    user: The email address of the account.
    query: String used to filter messages returned.
           Eg.- 'label:UNREAD' for unread Messages only.

  Returns:
    List of messages that match the criteria of the query. Note that the
    returned list contains Message IDs, you must use get with the
    appropriate id to get the details of a Message.
  """
  try:
    response = service.users().messages().list(userId=user, q=query).execute()
    messages = response['messages']

    while 'nextPageToken' in response:
      page_token = response['nextPageToken']
      response = service.users().messages().list(userId=user, q=query,
                                         pageToken=page_token).execute()
      messages.extend(response['messages'])

    return messages
  except errors.HttpError, error:
    print 'An error occurred: %s' % error
    if error.resp.status == 401:
      # Credentials have been revoked.
      # TODO: Redirect the user to the authorization URL.
      raise NotImplementedError()

后续步骤

熟悉如何向 Gmail API 请求授权后,您就可以开始处理消息、线程和标签了,如开发者指南中所述。

如需详细了解可用的 API 方法,请参阅 API 参考文档