서버 측 승인 구현

Gmail API에 대한 요청은 OAuth 2.0 사용자 인증 정보를 사용하여 승인되어야 합니다. 애플리케이션이 사용자를 대신하여 Google API에 액세스해야 하는 경우(예: 사용자가 오프라인) 서버 측 흐름을 사용해야 합니다. 이 방법을 사용하려면 클라이언트에서 서버로 일회성 승인 코드를 전달해야 합니다. 이 코드는 서버의 액세스 토큰을 얻고 토큰을 갱신하는 데 사용됩니다.

서버 측 Google OAuth 2.0 구현에 관한 자세한 내용은 웹 서버 애플리케이션에 OAuth 2.0 사용을 참고하세요.

목차

클라이언트 ID 및 클라이언트 비밀번호 만들기

Gmail API를 사용하려면 먼저 설정 도구를 사용해야 합니다. 이 도구는 Google API 콘솔에서 프로젝트를 만들고, API를 사용 설정하고, 사용자 인증 정보를 만드는 방법을 안내합니다.

  1. 사용자 인증 정보 페이지에서 사용자 인증 정보 만들기 > OAuth 클라이언트 ID를 클릭하여 OAuth 2.0 사용자 인증 정보를 만들거나 사용자 인증 정보 만들기 > 서비스 계정 키를 클릭하여 서비스 계정을 만듭니다.
  2. OAuth 클라이언트 ID를 만든 경우 애플리케이션 유형을 선택합니다.
  3. 양식을 작성하고 만들기를 클릭합니다.

이제 애플리케이션의 클라이언트 ID와 서비스 계정 키가 사용자 인증 정보 페이지에 나열됩니다. 자세한 내용을 보려면 클라이언트 ID를 클릭합니다. 매개변수는 ID 유형에 따라 다르지만 이메일 주소, 클라이언트 비밀번호, 자바스크립트 출처, 리디렉션 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 상태 코드를 확인하며, 이 상태 코드는 사용자를 승인 URL로 리디렉션하여 처리해야 합니다.

그 밖의 Gmail API 작업은 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 참조를 확인하세요.