본문 바로가기
SW 개발/REST API

Service Account(JWT)을 활용한 Google Calendar API 사용

by Kibua20 2020. 7. 12.

이전 Gmail API 포스팅에서는 Sever-side Web Application 기반으로 OAutho2 인증을 통해서 Access token을  받고 Gmail API를 사용하는 방법에 대해서 포스팅하였다.  본 포스팅에서는 Service Account를 기반한 OAuth 2.0 인증을 받고, Access token을 획득하여 Google Calendar API를 호출하는 과정을 설명하고 한다.  본 게시글은 Google OAutho2.0 의 Service account의 내용을 기반으로 설명하였으며, 실제 동작하는 python sample code도 포함되어 있습니다.

 

Server side web application : Gmail API 게시글

[모바일/REST API] - Google Gmail API 사용 방법 (1)

[모바일/REST API] - Google Gmail API 사용 방법 (2)

[모바일/REST API] - Google gmail API 사용 방법 (3) 

 

Service account는 end-user를 대신해 Google 서버에서 인증을 받고 API를 호출할 수 있다.  Service Account의 가장 큰 장점은 사용자 로그인이 없이 Access token을 발급 가능하다.  사용자가 명시적인 로그인이 없이도 Json Web Token을 기반으로 API scope에 대한 사용 권한을 받을 수 있고,  Access 토근을 통해서 Google API를 사용할 수 있다.  

 

한 가지 주의할 점은 사용자의 계정과 서비스의 계정은 별도의 계정이며, 사용자 데이터를 접근하기 위해서는 서비스 계정에 공유 권한을 부여해야 한다.  예를 들어, kibua20@gmail.com 의 사용자 계정에 kibua20-services@serverapi.iam.gservices.com 의 서비스 계정을 만들었다면 이 둘은 다른 계정으로 취급되고, kibua20 계정의 캘린더 데이터를 접근하기 위해서는 kibua20-services 계정에 캘린더 접근 권한은 사전에 허용해야 서비스 계정에서 사용자 계정의 캘린더 사용이 가능하다.


Service Account 사용하기 위해서는 아래와 같이 5단계로 진행해야 한다.

  1. Google API console 에서 Service Account 생성: Service account를 생성하면 Client ID와 Private Key를 할당받는다.
  2. JWT (Json Web Token) 생성: Header, Claim set, signature를 base64url safe 값으로 encoding 한다. 
  3. 구글 서버에서 JWT을 사용해서 Token을 request 한다.
  4. 구글 서버에서 Access Token을 response로 내려준다.
  5. Access token을 사용해서 Google API를 호출한다.

 

Google OAutho 2.0 Service account를 통한 인증

 

 

Google API Console에서 Service Accont 생성하기

Step 1. API 활성화

Google API Console에 로그인해서 Project를 생성하고 사용하고자 하는 API를 활성화합니다.  Project 생성 방법은 이전 Gmail API에서 설명했던 방법과 동일하기 때문에 본 포스트에서 설명을 생략합니다. API를 활성화는 방법은 Gmail API에서도 설명했듯이 1) 라이브러리 메뉴를 선택하고, 2) Calendar API를 검색 '사용 설정'을 선택합니다.  'API 사용해 보기'를 통해서 API에 대한 설명을 확인할 수 있으며, API에 대한 동작을 Web page 상에도 확인이 가능합니다. 

 

서비스 계정의 API 활성화

 

Step 2. 서비스 계정 생성하기 

API 활성 후 1) 왼쪽 사이드 메뉴에서 '사용자 인증 정보 생성'을 진행합니다. 2) 사용자 인증 정보 만들기에서 '서비스 계정'을 선택합니다. OAutho 2.0 클라이언트 ID 만들기에서는 '동의 화면' 구성을 먼저 진행하였지만, 서비스 계정은 사용자 동의 화면이 없기 때문에 '동의 화면' 구성을 할 필요는 없습니다. 

 

사용자 인증 정보 - 서비스 계정 만들기

 

서비스 계정을 선택하면 서비스 계정의 세부 정보를 입력해야 합니다. 아래와 같이 3) 서비스 계정 이름을 입력하고, 4) 서비스 계정의 권한을 입력하면 5) 서비스 생성이 완료됩니다. 서비스 계정이 생성되면 고유의 client ID가 부여됩니다.

 

사용자 인증 정보 - 서비스 계정 만들기 (세부 정보 입력)

 

Step 3. 서비스 계정 Private Key 만들기

서비스 계정을 생성한 다음 Private Key를 만드는 단계입니다. Private Key는 기존 Key를 재활용할 수 도 있고, 신규로 생성도 가능합니다. Private Key 생성되면 생성된 Key는 PC에만 다운로드되며,  Google API console에서는 다시 확인할 수 없기 때문에 잘 보관해야 합니다. 만일 PC에 저장한 Private Key를 분실하는 경우 이전 Key를 복구할 수 없고 신규로 만들어야 하고 어플리케이션 코드를 수정해야하기 때문에 주의해야 합니다. 

 

Private Key 만들기 과정은 1) 서비스 계정 선택 후 Key 만들기 선택, 2) JSON 타입으로 선택,3) 만들기 누르고, 4) Private Key가 PC로 저장됩니다. 

 

사용자 인증 정보 - 서비스 계정 만들기 (Private Key 성성 후 PC로 다운로드)

 

본 포스팅에서 예제로 만든 서비스 계정의 Private Key는 아래와 같습니다. Project ID, Private Key ID, Private Key 값, client ID, client id의 email, cert URL 이 작성되어 JSON 파일로 저장됩니다.  

 

서비스 게정의 Private Key JSON 파일

JWT (Json Web Token) 생성하기

JWT에 대한 설명은 JWT(JSON Web Token) Encoding 방법에서 자세히 설명하였습니다. Python에서 PyJWT 설치하고, jwt.encode() 함수를 사용해서 JSON token을 얻습니다. (참고)  JWT에 대한 설명은 아래 영상을 참고 바랍니다. JWT에 대해서 기술 측면에서 쉽게 설명되어 있습니다. 

 

https://www.youtube.com/watch?v=MUUqogMpGiA

 

아래 파이선 코드에서 PRIVATE_KEY_ID_FROM_JSON 와PRIVATE_KEY_FROM_JSON은 Goolge API console에서 다운로드한  Private Key JSON 파일의 값과 동일한 값으로 변경해야 합니다.  그리고, JWT의 payload에서는  iss 값은 서비스 계정을 입력하고, scope은 사용하고자 하는 API reference를 찾아서 필요한 API scope을 입력합니다. Scope이 여러 개를 포함하는 경우 space로 구분해서 모두 입력해야 합니다.  aud 값은 항상 https://oauth2.googleapis.com/token 로 설정합니다.  iat는 현재 시간 값으로 설정하고 exp 값은 iat+3600 (1시간)으로 설정하면 JWT을 얻을 수 있습니다. 

import jwt
import time
import json

def getSignedJWT():
    PRIVATE_KEY_ID_FROM_JSON ="e1eba38c4d7fef671273a6f62c46cbb15db3f854"
    PRIVATE_KEY_FROM_JSON= "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANB (생략) k4EcMKWPm\n-----END PRIVATE KEY-----\n"

    iat = time.time()
    exp = iat + 3600
    payload = {
                'iss': 'kibua20-service@calendarapi-282612.iam.gserviceaccount.com',
                'scope': 'https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/calendar.events.readonly',
                'aud':'https://oauth2.googleapis.com/token',
                'iat': iat,
                'exp': exp
            }
    additional_headers = {'kid': PRIVATE_KEY_ID_FROM_JSON}
    signed_jwt = jwt.encode(payload, PRIVATE_KEY_FROM_JSON, headers=additional_headers,algorithm='RS256')
    return signed_jwt.decode('utf8')

 

JWT을 Google 인증 서버로 requst 하여 Access Token 얻어오기

생성된 JWT을 구글의 인증 서버로 request를 합니다. grant type에는 urn:ietf:params:oauth:grant-type:jwt-bearer로 설정하고, assertion 값에는 앞에서 생성한 JWT로 parameter를 설정하고, request를 post 합니다.

 

 

 

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbG (생략) qQ

 

Python code로는 아래와 같이 구성할 수 있습니다.  URL은 Google 인증 서버로 설정하고, data에 gray_type과 assertion을 설정합니다.  

import jwt
import time
import requests
import json

def getAccessToken():
    signed_jwt = getSignedJWT()

    URL = 'https://oauth2.googleapis.com/token'
    data = {
        'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'assertion': signed_jwt
    }
    response = requests.post(URL, data=data)
     
    # 200 OK 코드가 아닌 경우 exception 발생
    response.raise_for_status()
    return response.json()['access_token']

request에 대한 repose 값의 json은 아래와 같습니다. acces_token 값이 저장되어 있고, 유효 기간 (expires_in) 값이 설정되어 있으며, token_type이  'Bearer'로 설정됩니다.  구글의 reference에서는 scope 이 같이 내려온다고 설명되어 있으나, 실제 동작에서는 scope은 생략된 상태로 내려옵니다. 

{
"access_token": "ya29.c.Ko8B0gczH2HViEpdhuTgcLb6jTWcb (중략) pY",
"expires_in": 3599,
"token_type": "Bearer"
}

 

 

Access Token으로 Calendar API 사용하기

Access Token을 얻어온 다음에는 Calendar API를 호출합니다.  주의해야 할 사항은 사용자 계정 (kibua20@gmail.com)과 서비스 계정은 별도의 계정이기 때문에 사용자 계정의 calendar에서 서비스 계정으로 캘린더를 공유해야 합니다. 캘린더 공유 권한이 없으면 'Access deny' 에러가 받게 됩니다.  사용자 계정의 calendar의 설정 메뉴에서 서비스 게정으로 공유하는 방법은 아래 그림과 같이 설정 > 특정 사용자의 공유에서 계정을 추가할 수 있습니다. 

 

 

사용자 계정의 캘린더를 서비스 계정으로 공유

 

 

Google Canldear API를 호출하는 code는 아래와 같습니다.  사용자 계정 (kibua20@gmail.com) 캘린더의 Calendar ID가 본 계정과 동일한 경우 Event를 다운로드하는 code입니다.  사용자 계정에서 캘린더에서 어제 날짜의 event는 받지 않고, 당일부터 향후에 설정된 값을 받기 위해서는 timeMin 값을 설정할 수 있습니다.  access_token은 request header에 포함하고 request get을 호출하면 JSON response 값으로 사용자의 캘린더 event를 내려줍니다. 

def get_calendar_events():
    access_token = getAccessToken()
    if (access_token ==None):
        print ('Error : Cannot get access token')
        return

    calendarId ='kibua20@gmail.com'
    curtime = time.strftime('%Y-%m-%dT00:00:00-0000', time.localtime(time.time()))
    URL = 'https://www.googleapis.com/calendar/v3/calendars/'+calendarId +'/events'+'?timeMin='+curtime

    request_header = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + access_token,
        "X-GFE-SSL": "yes"
    }
    response = requests.get(URL, headers=request_header)
    
    print(response)
    print(response.text) # response as json
    #print (response.json())  # response as dictionary

아래 예제는 테스트용으로 얻오온 repsonse의 JOSN에서 items이 사용자 계정 캘린더에 저장된 event 값으로  내려옵니다.

<Response [200]>
{
 "kind": "calendar#events",
 "etag": "\"p3 (생략) ek0g\"",
 "summary": "kibua20@gmail.com",
 "updated": "2020-07-08T05:49:45.476Z",
 "timeZone": "Asia/Seoul",
 "accessRole": "reader",
 "defaultReminders": [],
 "nextSyncToken": "(생략)",
 "items": [
  {
   "kind": "calendar#event",
   "etag": "(생략)",
   "id": "(생략)",
   "status": "confirmed",
   "htmlLink": "https://www.google.com/calendar/event?eid=N(생략)t",
   "created": "2020-07-08T05:25:55.000Z",
   "updated": "2020-07-08T05:49:07.008Z",
   "summary": "calendar event #1",
   "description": "Description #1 한글 포함",
   "creator": {
    "email": "kibua20@gmail.com",
    "self": true
   },
   "organizer": {
    "email": "kibua20@gmail.com",
    "self": true
   },
   "start": {
    "date": "2020-07-08"
   },
   "end": {
    "date": "2020-07-09"
   },
   "transparency": "transparent",
   "iCalUID": "(생략)",
   "sequence": 0,
   "reminders": {
    "useDefault": false
   }
  },
  

 

관련 글

[모바일/REST API] - Google Gmail API 사용 방법 (1) - Sample code

[모바일/REST API] - Google Gmail API 사용 방법 (2) - Sample code

[모바일/REST API] - Google gmail API 사용 방법 (3) - Sample code

[모바일/REST API] - JWT(JSON Web Token) Encoding 방법 (Python sample code)

[모바일/모바일 마케팅] - [용어 정리 #5] Google Analytics 개념 잡기

[모바일/모바일 마케팅] - [용어 정리 #1] Google 광고 ID / 추적 URL / 리퍼러 / 추적 알고리즘

[개발환경] - WSL 2.0 설치 방법 및 기능 확인

[블로그 관리] - 초보 블로그 관리: 서치 콘솔 등록부터 Adsense 낙방 후 2차 성공!

[개발환경] - Ubuntu 에서 SW 개발 Tool 설치




댓글