2011. 4. 20. 17:38

Google Code API(OAuth인증 & Google Calendar)를 VC9.0(liboauth)에서 구현

본 포스트에서는 Google Code API를 Web browser 환경이 아닌 VC9.0 C++로 빌드된 Windows 환경에서
사용하는 방법에 대해서 설명합니다. 물론, oauth라는 표준화된 인증 체계를 따르므로, Google Code API 뿐만
아니라 다른 Open API(예, Twitter)에서도 응용 가능합니다.

Google에 Domain 등록하고 Consumer Key, Secret 발급 받기

http://code.google.com/intl/ko/more/
에 들어가면 수많은 Google API를 볼 수 있습니다.
그 중, "Google 계정 인증" 부분을 우선 확인해 보도록 하겠습니다.
왜냐하면, 계정 인증이 우선 이뤄져야 이후의 다른 API를 사용할 수 있기 때문입니다.

의 설명에서 지원되는 인증 방식으로는
  • oauth
  • authsub
  • clientlogin
  • openid
  • 혼합형 프로토콜

가 있습니다.

본 포스트에서는 oauth를 선택하였습니다. (특별한 이유는 없지만, 2011/03/21 - [Research/Etc] - oauth+openssl+curl 삼종 세트 VC에서 빌드(build)하기 (재배포팩 필요없이)의 빌드 환경을 이미 구성했기 때문입니다. 물론, oauth가 보안성도 높으면서도 대체적인 표준 인증 방식이기 때문에, 다른 웹서비스(예, twitter)에서도 쉽게 응용될 수 있는 장점도 중요합니다.)

사전에 준비해야 할 것중 하나가 Google에 Application을 등록하는 것입니다.
물론, anonymous로 해도 되지만, 되도록이면 Google에 Application을 등록해서, 인증시 사용자에게
보다 안정적인 모습으로 다가가기 위해서 입니다.
첨언하자면, Windows Vista이상의 UAC를 연상하시면 됩니다.


즉, 위와 같이 notepad.exe와 testauth.exe의 UAC는 다르게 표시됩니다. 즉, Code Sign의 여부인데, 없는 경우
"알 수 없는 프로그램이 컴퓨터에 엑세스하려고 합니다."와 같은 문구가 발생하게 됩니다.
이와 유사하게, Google에 Application을 등록하면, 보다 사용자 친숙하게 인증 절차가 진행됩니다.

우선,
http://code.google.com/intl/ko/apis/accounts/docs/RegistrationForWebAppsAuto.html
에 들어가서 제품을 등록하여, oauth에 필요한 인증키값들을 제공받습니다.
그러기 위해서는, 아래와 같은 스텝이 필요합니다. (트위터에 비해 복잡하군요)

를 클릭하면, 다음과 같은 google 계정으로 들어갑니다.

그럼, 아래와 같이 "Add Domain"하고 "Manage greenfishblog.tistory.com"을 선택해야 합니다.
(즉, Google API 사용을 위해서는 사용자의 Domain이 필요합니다. 즉, www.myapp.com과 같은 Domain이
필요합니다. 여기에서는 http://greenfishblog.tistory.com으로 정의합니다. Domain으로 정의되기 때문에,
http://www.myhome.com/~myapp과 같은 형식은 지원하지 않았습니다.)

그럼 해당 Domain이 당신 "소유"라는 것을 증명하기 위한 절차가 남았습니다.
위의 "Manage greenfishblog.tistory.com"의 링크로 들어가면 아래와 같이 "소유권 확인" 창이 뜹니다.


greenfishblog.tisotry.com에 Google에서 지정한 이상한 이름의 파일을 업로드 하던지, meta tag를 추가하던지, 등등의 인증 방법을 선택하게 됩니다.
저는 나름 가장 편했던, Meta Tag 추가를 선택하였습니다.
그러면, greenfishblog.tistory.com의 index.html등에 알려준 Meta tag를 넣으면 됩니다.

중간의 meta tag를 복사한뒤, tistory를 수정해 보도록 하겠습니다.


다음과 같이 meta tag를 추가한뒤 저장합니다.

이제 위의 "소유권 확인" 창에 있는 "확인"을 누르면 됩니다.

드디어, 아래와 같이 인증이 성공되었습니다.



동의를 하게 되면, 아래와 같이 간단한 sheet를 작성해야 합니다.
authsub는 사용하지 않고 oauth를 사용할 것이기 때문에,
"Target URL path prefix"는 그냥 URL을 넣었습니다.



Save하게 되면, 아래와 같이 OAuth Consumer Key / Consumer Secret 값을 알려 주는데,
OAuth인증시 사용하게 됩니다.



http://code.google.com/intl/ko/apis/accounts/docs/OAuth_ref.html
에 따르면,
request token url : https://www.google.com/accounts/OAuthGetRequestToken
access token url : https://www.google.com/accounts/OAuthGetAccessToken
authorize url : https://www.google.com/accounts/OAuthAuthorizeToken
와 같이 나타납니다.

그럼, oauth인증을 위한

  • consumer key
  • consumer secret
  • request token url
  • access token url
  • authorize url

이 완료되었습니다.

Google OAuth 인증 Process를 통해 Access Token 발급 받기

이제 부터는 OAuth 인증을 통해 access token을 구하는 과정을 알아보겠습니다.
기본적인 OAuth는 공식, 비공식의 경로를 통해 프로토콜을 습득하시기 바랍니다.
쉽게 설명하자면, "휴대폰 인증"을 생각하시면 됩니다.
최근 실명 확인을 위해 "휴대폰 인증"을 하게 되는데, 이러한 SMS 문자를 통해 인증 번호를 전달하는 방식과 유사합니다.

우선, 2011/03/21 - [Research/Etc] - oauth+openssl+curl 삼종 세트 VC에서 빌드(build)하기 (재배포팩 필요없이)로 이뤄진 프로젝트를 가정하겠습니다. 즉, liboauth의 oauth method를 빌드하고 호출할 수 있는 환경을 가정하겠습니다.

그리고, Google OAuth에 대해서도 사전 지식이 있으면 도움이 됩니다.

설치형 Application의 Google 인증 방법, Google OAuth Guide 등의 글을 읽어 보십시요.

다음과 같은 Step으로 진행됩니다. (인증과정은 1~3입니다)

  1. OAuth 인증을 위해 Request Token을 구한다.
  2. Request Token을 통해 Web Browser를 띄워 사용자로 부터 Verify 값을 구하도록 유도한다.
  3. Verify 값으로 Access Token을 구한다.
  4. Access Token으로 Google Calendar API로 json 혹은 xml 값을 구한다.
    단, 그때 HTTP 301/302 redirect 처리를 해주도록 한다. (Google Calendar API 스펙)

과 같습니다.
(참고로, 아래 예제 코드들을 통해 동적 생성된 리턴값들을 free() 해줘야 합니다.)

lpszRequestUriA = oauth_sign_url2("https://www.google.com/accounts/OAuthGetRequestToken?oauth_callback=oob&scope=http://www.google.com/calendar/feeds", 
			  NULL, 
			  OA_HMAC, 
			  NULL, 
			  발급받은 Key, 
			  발급받은 Secret, 
			  NULL, 
			  NULL);
if (NULL == lpszRequestUriA)
{
	dwRtnValue = ERROR_SYSTEM_IMAGE_BAD_SIGNATURE;
	ASSERT(FALSE);
	goto FINAL;
}

와 같이 호출합니다. Key와 Secret는 위에서 등록된 값을 구하시기 바랍니다. (Google 문서에 따르면, anonymous도 가능하다고 합니다.)

그러면 lpszRequestUriA에는
https://www.google.com/accounts/OAuthGetRequestToken?oauth_callback=oob&oauth_consumer_key=greenfishblog.tistory.com&oauth_nonce=CQrE4hPPCkz8xTRp8
&oauth_signature_method=HMAC-SHA1&oauth_timestamp=...
와 같은 값이 전달됩니다.

그다음 아래와 같이 요청을 합니다.
lpszResponseA = oauth_http_get(lpszRequestUriA, NULL);
if ((NULL == lpszResponseA) || ('\0' == lpszResponseA[0]))
{
	dwRtnValue = ERROR_DEVICE_NOT_CONNECTED;
	ASSERT(FALSE);
	goto FINAL;
}

그러면 lpszResponseA에서는,
oauth_token=XXXXXX&oauth_token_secret=XXXXXXX&oauth_callback_confirmed=true
와 같은 값이 전달됩니다.

그다음 아래와 같이 Parsing 합니다.
if (FALSE == ParseResponse(lpszResponseA, 
				   &lpszRequestToken, 
				   &lpszRequestTokenSecret))
{
	dwRtnValue = ERROR_XML_PARSE_ERROR;
	ASSERT(FALSE);
	goto FINAL;
}

그럼, lpszRequestToken과 lpszRequestTokenSecret 값이 들어왔습니다.

참고로, ParseResponse는 다음과 같은데,
liboauth의 Test 경로의 oauthexample.c를 참고하면 됩니다.


이제 Request Token을 통해 Access Token을 구할때가 되었습니다.


하면 strCallbackUrl은 다음과 같습니다.
https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token=XXXXXXXXX

이제 Application에서는 위 CallbackUrl을 브라우저로 띄우도록 합니다.
아래 함수를 참고하세요.


그럼 다음과 같이 됩니다.


사용자는 자신의 계정을 통해 Google에 로그인을 하게됩니다.
이 과정에서 해당 Application은 사용자 Google 계정의 id, password를 제공받지 않습니다.


로그인을 하면 위 화면으로 Redirect되는데, "Google 캘린더"를 액세스한다는 내용입니다.
특징으로는 "greenfishblog.tistory.com 사이트"라는 말이 뜹니다. anonymous로 했을 때에는,
위의 UAC 예로 든 것 처럼, 약간의 경고성의 문구가 발견됩니다.


만일 사용자가 "액세스 허용"을 눌렸을 경우, 위 창으로 연결되며, 해당 페이지는 인증코드를 알려줍니다.

즉, Application에서는 Web을 띄우면서 Modal 창을 함께 띄워 사용자로 부터 위 인증코드를 입력 받도록 설계해야 합니다.

그럼, 사용자로 부터 받은 인증코드를 lpszVerifier에 저장되었다고 가정합니다.
lpszVerifier = "vH9hTW8MXVQTU21vONTCG0iJ"


와 같이 하면, strAccessTokenUrl은
https://www.google.com/accounts/OAuthGetAccessToken?oauth_verifier=YYYYY
와 같이 됩니다.


와 같이 호출하면 lpszRequestUriA에는 다음값이 들어갑니다.
https://www.google.com/accounts/OAuthGetAccessToken?oauth_consumer_key=greenfishblog.tistory.com&oauth_nonce=Pjj0GQ8Ss531bQm5MCb6dbO&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1303276761&oauth_token=...

그럼 최종적으로 다음을 요청하여 Access Token을 구합니다.

그 결과로 lpszResponse는 다음과 같습니다.
oauth_token=XXXXX&oauth_token_secret=YYYYY
와 같이 됩니다.

다음과 같이 Parsing하여 최종적으로 Access Token을 구합니다.

그럼 마침내 lpszAccessTokenA, lpszAccessTokenSecretA를 구했습니다.
해당 값을 Application에서 적당히 암호화하여 파일로 잘 저장해 보관해야 합니다.
저장된 값을 사용하면, 더이상 OAuth 인증을 시도할 필요는 없어집니다.
Google에 따르면, 해당 Access Token은 Long Live 속성을 가진다고 합니다.

Google Calendar API를 사용하기

이제부터 Access Token으로 Google Calendar API와 연동해 보도록 하겠습니다.

사전에 Google Calendar API 문서도 한번 읽어 보시기 바랍니다.
해당 문서에 따르면, 사용자의 모든 Calendar를 구하기 위해서는
https://www.google.com/calendar/feeds/default/allcalendars/full
API를 사용하라고 나와 있습니다.

웬만하면, oauth_http_get(...)을 이용하면 xml/json 데이터를 구할 수 있지만,
위 API는 그렇게 하면 301/302(Moved Permanently) 오류가 발생합니다.
Google Calendar API 문서의 Retreiving events 섹션을 보면, Calendar API는 예외적으로 302 redirect를 발생시킨다고 합니다. 이는 session을 유지시켜 성능을 향상시키기 위해서 그렇다고 합니다.
그리고, redirect url에서 gsessionid 값을 추가로 알려주는데, 해당 값을 다시 넣어줘야만 200 OK 가 발생합니다.

이를 구현하기 위해서는 liboauth가 약간 수정(기능 추가)되어야 합니다.

liboauth 경로의 oauth_http.cpp에 다음 함수를 추가합니다.

해당 함수는 oauth_http_get 함수의 확장된 버전입니다.
즉, 주어진 parameter들을 모두 oauth 스펙에 맞게 sign한 다음 GET 요청합니다.
만일 301/302 오류가 발생한 경우,
curl의 effect url을 찾아 다시한번 sign하고 요청합니다.
단, lpszAddParamFromRedir는 redirect url의 새로운
parameter를 전달하는데, 만약 "gsessionid"를 전달하면, redirect시에 추가된 gsessionid를 새로 추가하여 요청하게 됩니다.

위 함수를 빌드하면 오류가 발생하는데,
oauth_http.cpp에 있는 oauth_curl_get(...) 함수를 다음과 같이 수정해야 합니다.


다음 argument가 추가되었습니다.
  • bFollowLocation
    CURL의 CURLOPT_FOLLOWLOCATION 사용 여부. redirect되는 경우, 따라가도록 한다.
    그려면, redirect되는 url(effect url)을 구할 수 있다.
  • pnResponseCode
    HTTP 요청 결과 Response Code 참고
  • lpszEffectUrl
    Redirect시 effect url을 구한다. bFollowLocation이 TRUE일때 효과가 있다.
  • nCchEffectUrl
    lpszEffectUrl의 버퍼 크기

그리고, gsessionid와 같은 값을 파싱하기 위한 아래의 함수를 추가하십시요.



그럼 최종적으로 다음과 같이 호출할 수 있습니다.

이렇게 호출하면, lpszRtnValue는 다음과 같은 Calendar API json 포맷의 데이터가 들어옵니다.
{"version":"1.0","encoding":"UTF-8","feed":{"xmlns":"http://www.w3.org/2005/Atom","xmlns$openSearch":"http://a9.com/-/spec/opensearchrss/1.0/","...
(alt=json parameter가 없으면 xml 데이터가 들어옵니다.)

이로써 Google OAuth와 Calendar API를 Windows 환경의 Application에서 호출하는 방법을 알아봤습니다.
현재는 GET 방식의 API만 호출했는데, POST 방식의 API 호출 부분도 확인을 해 봐야 할 것입니다.

그리고, Google 말고도 Twitter OAuth와 API도 비슷한 방법으로 구현됩니다.
(Twitter는 Google 보다 훨씬 단순하고 쉽게 구현됩니다.)

Special Thank you

이상으로 포스트를 마치며,
이글을 작성하는데 도움을 주신 liboauth Open Source 관리자이신 robin 님에게 많은 도움을 받았습니다.
다시한번 감사하다는 말을 드리고 싶습니다.