데스크탑 애플리케이션에서의 OAuth 인증

From myRuby.net, 5 months ago, 0 views

OAuth - 오픈 API를 위한 인증 표준이 가진 장점 중 하나는 아래 나열된 다양한 플랫폼에서 매시업을 개발할 수 있도록 준비가 잘 되어있다는 점이다.

 

 

각 플랫폼에서 사용할 수 있는 베스트 프랙티스가 기술되어 있으므로 이를 적절하게 수용하면 된다. 웹 애플리케이션의 경우는 callback URL을 주고받으며 오픈 ID를 사용하는 것과 비슷한 경험으로 매시업 인증을 행할 수 있다. 여기에 대한 예제는 많으므로 이 글에서는 데스크탑 애플리케이션의 경우를 살펴보자. 오랜만에 루비 코코아 프로그래밍을 해보도록 하자.

 

아래 화면은 최근에 만들고 있는 DropBox라는 애플리케이션의 화면이다. 애플리케이션을 처음 띄우면 로그인을 하도록 안내 화면이 나타난다. 여기서 로그인 버튼을 누르면 OAuth 인증 절차를 시작한다.

 

 

인증 준비

시작은 어디나 같다. 리퀘스트 토큰을 발급받는 일이다.

 

  1. @consumer = OAuth::Consumer.new CONSUMER_KEY, CONSUMER_SECRET,
      :site => 'https://api.openmaru.com',
      :access_token_path => '/oauth/access_token/springnote'

  2. @request_token = @consumer.get_request_token

 

사용자 동의

이제 사용자에게 권한을 위임받아야 한다. 브라우저(WebView)를 담은 윈도우를 하나 만들어 화면에 출력한 다음, 이 브라우저를 Authorize URL로 이동시킨다.

 

  1. # WebView 컨트롤에 연결된 아웃렛
  2. ib_outlet :webview
  3.  

    @login_window.makeKeyAndOrderFront(self)
    @webview.setMainFrameURL @request_token.authorize_url

 

그 결과는 아래 화면이다.

 

 

여기에 사용자가 오픈ID를 입력하면, 권한을 요구하는 화면이 나온다.

 

 

이 화면에서 사용자는 특정 매시업에게 접근 권한을 줄지 말지는 결정할 수 있다.

 

사용자 행동을 감지한다

우리가 개발한 매시업은 사용자가 승인/거절을 선택할 때까지 기다리고 있다. 승인을 한 후에만 엑세스 토큰을 얻어서 다음 작업을 수행할 수 있기 때문에, 지금은 기다리는 일 말고는 할 일이 없다. 그렇다면 어떻게 긴 기다림을 끝낼 수 있을까? 두가지 방법이 있다.

 

첫째, 사용자가 알려주기를 기다린다. 위에서 승인 버튼을 누른 다음, 사용자자 '매시업 계속 사용하기' 등의 버튼을 누르게 유도하고, 이 버튼이 눌리면 다음 작업(액세스 토큰 발급)을 진행하다. 가장 쉬운 방법이고, 플리커등의 애플리케이션에서 이런 방법을 사용하고 있다.

 

둘째, WebView의 이벤트를 받아서, 승인/거절을 누른 행동을 감지한다. 다행히 오픈마루 API 센터는 승인 또는 거절 후의 URL이 정해져있다. URL만 감시하면 어렵지 않게 승인/거절 여부를 알 수 있다. 여기서는 조금 번거롭지만 이 방법을 사용하자. 먼저, web_view의 frameLoadDelegate를 컨트롤러에서 처리할 수 있도록 등록해준다. 아래 화면처럼 인터페이스 빌더에서도 할 수 있고, 코드로도 할 수 있다.

 

 

이렇게 등록하면 로드가 완료된 시점에 didFinishLoadForFrame이 호출된다. 여기서 URL을 비교해서 성공/실패 여부를 판단한다.

 

  1. def webView_didFinishLoadForFrame(sender, frame)
      case @webview.mainFrameURL.to_s
      when 'https://api.openmaru.com/oauth/authorize_success'
        # 성공
      when 'https://api.openmaru.com/oauth/authorize_failure'
        # 실패
      end
    end

 

사용자가 OK 한다면?

사용자가 승인하면, 비로소 액세스 토큰을 얻을 수 있다.

 

  1. @access_token = @request_token.get_access_token

 

이제 로그인 프로세스가 끝났다는 통지를 하고 로그인 창을 닫는다.

 

  1. def notify_and_close(msg)
      @login_window.orderOut self
      nc = OSX::NSNotificationCenter.defaultCenter   
      nc.postNotificationName_object(msg, self)
    end

    notify_and_close('LoginSuccess')

 

사용자가 거부하면?

큰일이다. 사용자가 무슨 이유에서건 매시업 사용을 거부했다. 여기서는 간단한 경고창을 출력하고, 통지한다.

 

  1. OSX::NSRunAlertPanel('스프링노트 로그인', '실패했습니다', '확인', nil, nil)
    notify_and_close('LoginFailed')

 

 

이벤트를 받으면?

액세스 토큰 발급 여부에 따라 화면을 그려준다.

 

  1. notify_on('LoginSuccess') {|noti| check_main_window  }

    def check_main_window
      win = access_token ? @main_window : @login_window
      win.makeKeyAndOrderFront self
    end

 

위에서 notify_on은 Rucola에서 정의한 DSL로 꽤 편리하다.

 

이렇게 로그인 프로세스가 끝났다. 정리해보자. 데스크탑 애플리케이션에서 특별히 주의할 점은 크게 두가지다.

 

  1. 리퀘스트 토큰을 발급받은 후, 브라우저를 띄워서 사용자에게 접근 권한을 요청한다.
  2. 사용자가 수락/거절한 후, 작업을 재개한다.

 

지금까지 이 글을 읽은 사람이라면, 이런 관정을 생각보다 어렵지 않은 것으로 여길거라 믿는다.

 

키체인에 액세스 토큰을 저장

사용자가 애플리케이션을 띄울 때마다 로그인을 해야할까? 물론, 그걸 선호하는 사용자도 있겠지만 적어도 나는 아니다. 로그인을 저장해두기를 원한다. 이를 위해서는 액세스 토큰을 저장해야한다. 하지만 액세스 토큰은 패스워드나 마찬가지다. 잘 간수해야한다. 일반 텍스트 문서로 아무 곳에나 던져두면 안된다 :) 다행히 OS X에는 키체인이라는 안전한 저장소가 있다. 여기가 좋겠다! 루비 코코아에서 키체인에 접근하는 방법은 이 문서에 잘 설명되어 있다.

 

  1. KEYCHAIN_SERVICE = 'OAuth access token for springnote'
    KEYCHAIN_ACCOUNT = 'springnote'
    KEYCHAIN_SEPERATOR = '--'

    def self.add_key_chain(token)
      password = [token.token, KEYCHAIN_SEPERATOR, token.secret].join
     
      OSX::SecKeychainAddGenericPassword nil,
        KEYCHAIN_SERVICE.length, KEYCHAIN_SERVICE,
        KEYCHAIN_ACCOUNT.length, KEYCHAIN_ACCOUNT,
        password.length, password,
        nil
    end

 

구동시 저장된 토큰을 찾는다

애플리케이션을 실행하면, 맨 처음 할 일을 저장된 토큰이 찾는 일이다. 있다면 이 토큰을 사용하고, 없으면 OAuth 인증 절차를 처음부터 따라야 한다. 키체인에서 토큰을 찾아보자.

 

  1. def self.token_from_key_chain
      status, *data = OSX::SecKeychainFindGenericPassword nil,
        KEYCHAIN_SERVICE.length, KEYCHAIN_SERVICE,
        KEYCHAIN_ACCOUNT.length, KEYCHAIN_ACCOUNT
      return false unless status.to_i == 0
     
      password_length = data.shift
      password_data   = data.shift
      password = password_data.bytestr(password_length)
     
      token, secret = password.split('--')
      return false if !token || !secret
     
      OAuth::AccessToken.new(CONSUMER, token, secret)
    end

 

여기까지 구현하니, 마음이 놓인다.

 

이제 사용자의 리소스를 다룬다

이제 남은 일은 사용자가 수락한 액세스 토큰을 활용해 리소스를 다루는 일 뿐이다. DropBox는 데스크탑의 파일을 드래그 & 드랍으로 쉽게 스프링노트에 업로드 할 수 있게 해주는 툴이다. 원래는 대시보드 위젯으로 만들었었는데, 지금 루비 코코아로 포팅하는 중이다. 아래 화면에서는 DOC, HWP 등의 파일을 드랍하면 그 파일을 스프링노트 문서로 변환해서 출력해준다.

 

 

아래 화면에는 URL에 지정된 리소스에 드랍된 파일을 첨부한다. 특히 Skitch와 함께 사용할 때 정말 유용하고 강력하다. Skitch를 이용하기 위해서 DropBox가 존재한다고 해도 과언이 아니다. 하하

 

 

아래는 사용 예를 보여주는 간단한 데모 비디오다. 좀 유용해 보일려나? 처음 1분 정도는 위에서 설명한 OAuth 인증 과정을 살펴볼 수 있다.

 

 

DropBox에 대한 내용과 전체 소스 코드는 조금 더 개발한 후에 공개할 예정이다. 이상 OAuth 전문 블로거(-_-) deepblue.

 

 

comments

No comments yet.

You must be logged in to add your own comment.