2012年2月7日火曜日

AIRでOAuth2認証するには?

AIRでAdSenseから取得したデータを表示させてみようと思った。

Getting Started - AdSense Management API - Google Code

しかし、そのためにはOAuth2認証を通す必要があるようだ。では、AIRでOAuth2認証するにはどうしたらいいのか?


「API Console」で必要な情報をそろえる

まずOAuth2で必要な情報を集めるために、API Consoleに逝く。

新しいプロジェクトを作る。ここでは「OAuth2Test」とした。

プロジェクトを作ると自動的に開く「services」で「AdSense Management API」を「ON」する。

「API Access」ページに逝って「Create an OAuth 2.0 client ID」という青くてデカいボタンクリック。

「Product Name」を入力して「Next」。ここでは「OAuth2Test」とした。「Product Logo」は放っておいておk。

「Application Type」として「Installed Application」を選んで「Create client ID」。

すると、「Client ID」「Client secret」「Redirect URIs」がもらえる。これらがOAuth2で必要な情報だ。



ユーザーに許可を求めるページを表示して「code」を取得

ユーザーにAdSenseへのアクセスの許可を求めるページを表示して、「code」を取得する必要がある。

そのために、StageWebViewを使う。コードはこんな風。

protected function onClickBtnStartOAuth2():void {
    swv = new StageWebView();
    swv.addEventListener(Event.COMPLETE, onCompleteSWV);
    function onCompleteSWV(evt:Event):void {
        const codeSubstringKey:String = "code=";
        var title:String = swv.title; //「code」はタイトルに含まれている
        trace("onCompleteSWV() title=[" + title + "]");
        var idx:int = title.indexOf(codeSubstringKey);
        if (idx != -1) {
            
            //「code」を切り出し
            code = title.substring(idx + codeSubstringKey.length);
            trace("onCompleteSWV() code=[" + code + "]");
            
            //codeを元にaccessTokenをリクエスト(grant_typeはInstalledAppの場合はこれで固定)
            getAccessToken.send({"code":code, "client_id":CLIENT_ID, "client_secret":CLIENT_SECRET, "redirect_uri":REDIRECT_URI, "grant_type":"authorization_code"});
            
        }
    }
    swv.stage = stage; //WebView表示開始(WebViewはディスプレイリストに追加できないのでフルスクリーンになる)
    swv.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
    swv.loadURL(createAuthURL()); //許可ページリクエスト
}


protected function createAuthURL():String {
    var url:String = "https://accounts.google.com/o/oauth2/auth?" //ここに投げることになってる
        + "scope=" + escapeMultiByte("https://www.googleapis.com/auth/adsense.readonly") //許可を得たいサービス
        + "&redirect_uri=" + REDIRECT_URI
        + "&response_type=code" //InstalledAppだとこの値で固定
        + "&client_id=" + CLIENT_ID 
        return url;
}

まず、StageWebViewをフルスクリーンで表示して許可を求めるページを表示する。

許可ページのURLをcreateAuthURL()で作る時に、redirect_uriとclient_idに、API Consoleでもらった情報を使う。

もう一つのポイントは「scope」の指定の仕方。このscopeの種類はここに書いてあった。

Getting Started - AdSense Management API - Google Code

多分、他のAPIのドキュメントにも同様の記述があるのだろうと思われ。

許可ページでユーザーが許可を出すとページが切り替わる。そのページのタイトル部分に必要な「code」が含まれているので切り出す。


「access_token」を取得

codeを切り出したと同時に、codeを使ってaccess_token取得のリクエストを「https://accounts.google.com/o/oauth2/token」に投げる。

ここで注意が必要なのは

・このURLへのリクエストはPOSTで行う(GETは受け付けない)
・レスポンスはJSONで返ってくる

ということだ。

今回はこのように、URLへのリクエストにHTTPServiceを使うことにした。

<!-- JSONで返ってくるのでresultFormatをtextに。このURLにはPOSTでリクエストをなげることになってる。 -->
<s:HTTPService id="getAccessToken" url="https://accounts.google.com/o/oauth2/token" method="POST"
               resultFormat="text" result="onResultGetAccessToken(event);" fault="onFaultGetAccessToken(event);"/>

リクエストが成功した時の処理はこんな風。

protected function onResultGetAccessToken(evt:ResultEvent):void {
    var result:String = getAccessToken.lastResult as String;
    taAccessToken.text = result;
    trace("onResultGetAccessToken():" + evt + "\n" + result);
    var json:Object = JSON.parse(result); //JSONをパース
    accessToken = json.access_token;
    trace("onResultGetAccessToken():access_token=" + accessToken);
    swv.stage = null; //WebViewを非表示にする
}

JSONはFP11.0/AIR3.0から使えるようになったクラスだ。最近のREST型のサービスのレスポンスはXMLではなくほとんどJSONなので、そろそろサポートしてくれないと困っちゃうわいな。

今回はFB4.6で開発したので問題なかったけど、FP10系も対応させたいと思うと独自にJSONを扱えるライブラリを組み込むことになりそうだ。

そして、このリクエストが完了した時点で「swv.stage = null;」してWebViewを非表示にするところも重要。

ところで、access_tokenが取得できたらOAuth2認証はできたということだ。つまり、ここまでで認証処理はおしまい。


AdSenseの日毎の売上レポートをリクエストする

ちゃんと認証できたのかを確かめるために、実際にAdSenseに2012年1月の日毎の売上レポートをリクエストしてみる。

このリクエストに対応するのは「https://www.googleapis.com/adsense/v1.1/reports」だ。

AdSenseに限らないけど、GoogleのAPIでどんなメソッド(リクエスト先)があるかや、そのパラメーターにはどんなものがあるかは「APIs Explorer」でわかる。

Google APIs Explorer

今回もまた、このようにHTTPServiceを使う。

<!-- JSONで返ってくるのでresultFormatをtextに。 -->
<s:HTTPService id="reportsGen" url="https://www.googleapis.com/adsense/v1.1/reports" resultFormat="text"
               result="onResultReportsGen(event);" fault="onFaultReportsGen(event);"/>

実際にリクエストを送る部分はこんな風。

protected var startDate:String = "2012-01-01";
protected var endDate:String = "2012-01-31";
protected var dimension:String = "DATE";
protected var metric:String = "EARNINGS";
public function requestReportsGen():void {
    trace("requestReportsGen()");
    reportsGen.send({"startDate":startDate, "endDate":endDate, "dimension":dimension, "metric":metric, "key":API_KEY, "access_token":accessToken});
}

ここでaccess_tokenが必要なのだ。

レスポンスはまたJSONで返ってくるので、あとはグラフにするなど、好きな処理をすればいい。今回は単にTextAreaに表示させた。



全コード

最後に、全コードをこぴぺ。個人情報の部分は「xxx」した。

もちろん、このコードをどのように使っていただいても結構です。ただし、無保証、サポート無しです。

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                       xmlns:s="library://ns.adobe.com/flex/spark" 
                       xmlns:mx="library://ns.adobe.com/flex/mx"
                       width="640" height="480"
                       >
  
  <!-- 各種定義 -->
  <fx:Declarations>
    
    <!-- JSONで返ってくるのでresultFormatをtextに。このURLにはPOSTでリクエストをなげることになってる。 -->
    <s:HTTPService id="getAccessToken" url="https://accounts.google.com/o/oauth2/token" method="POST"
                   resultFormat="text" result="onResultGetAccessToken(event);" fault="onFaultGetAccessToken(event);"/>
    
    <!-- JSONで返ってくるのでresultFormatをtextに。 -->
    <s:HTTPService id="reportsGen" url="https://www.googleapis.com/adsense/v1.1/reports" resultFormat="text"
                   result="onResultReportsGen(event);" fault="onFaultReportsGen(event);"/>
    
  </fx:Declarations>
  
  <!-- スクリプト -->
  <fx:Script>
    <![CDATA[
      
      
      import mx.rpc.events.FaultEvent;
      import mx.rpc.events.ResultEvent;
      
      
      //OAuth2の許可ページ表示用WebView
      protected var swv:StageWebView;
      
      //APIs Consoleでもらった情報
      protected const CLIENT_ID:String ="xxx";
      protected const CLIENT_SECRET:String = "xxx";
      protected const REDIRECT_URI:String = "urn:ietf:wg:oauth:2.0:oob"; //InstalledAppだとこの値は固定らしい。「http://localhost」だとエラー出た。
      protected const API_KEY:String = "xxx";

      //欲しい情報
      [Bindable] protected var code:String; //accessTokenをもらうために必要
      [Bindable] protected var accessToken:String; //APIにアクセスするために必要
      
      
      protected function onClickBtnStartOAuth2():void {
        swv = new StageWebView();
        swv.addEventListener(Event.COMPLETE, onCompleteSWV);
        function onCompleteSWV(evt:Event):void {
          const codeSubstringKey:String = "code=";
          var title:String = swv.title; //「code」はタイトルに含まれている
          trace("onCompleteSWV() title=[" + title + "]");
          var idx:int = title.indexOf(codeSubstringKey);
          if (idx != -1) {
            
            //「code」を切り出し
            code = title.substring(idx + codeSubstringKey.length);
            trace("onCompleteSWV() code=[" + code + "]");
            
            //codeを元にaccessTokenをリクエスト(grant_typeはInstalledAppの場合はこれで固定)
            getAccessToken.send({"code":code, "client_id":CLIENT_ID, "client_secret":CLIENT_SECRET, "redirect_uri":REDIRECT_URI, "grant_type":"authorization_code"});
            
          }
        }
        swv.stage = stage; //WebView表示開始(WebViewはディスプレイリストに追加できないのでフルスクリーンになる)
        swv.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
        swv.loadURL(createAuthURL()); //許可ページリクエスト
      }
      
      
      protected function createAuthURL():String {
        var url:String = "https://accounts.google.com/o/oauth2/auth?" //ここに投げることになってる
        + "scope=" + escapeMultiByte("https://www.googleapis.com/auth/adsense.readonly") //許可を得たいサービス
        + "&redirect_uri=" + REDIRECT_URI
        + "&response_type=code" //InstalledAppだとこの値で固定
        + "&client_id=" + CLIENT_ID 
        return url;
      }
      
      
      protected function onResultGetAccessToken(evt:ResultEvent):void {
        var result:String = getAccessToken.lastResult as String;
        taAccessToken.text = result;
        trace("onResultGetAccessToken():" + evt + "\n" + result);
        var json:Object = JSON.parse(result); //JSONをパース
        accessToken = json.access_token;
        trace("onResultGetAccessToken():access_token=" + accessToken);
        swv.stage = null; //WebViewを非表示にする
      }
      
      
      protected function onFaultGetAccessToken(evt:FaultEvent):void {
        trace("onResultGetAccessToken():" + evt);
      }
      
      
      protected var startDate:String = "2012-01-01";
      protected var endDate:String = "2012-01-31";
      protected var dimension:String = "DATE";
      protected var metric:String = "EARNINGS";
      public function requestReportsGen():void {
        trace("requestReportsGen()");
        reportsGen.send({"startDate":startDate, "endDate":endDate, "dimension":dimension, "metric":metric, "key":API_KEY, "access_token":accessToken});
      }
      
      
      public function onResultReportsGen(evt:ResultEvent):void {
        var result:String = reportsGen.lastResult as String;
        trace("onResultReportsGen():" + evt + "\n" + result);
        taReportsGen.text = result;
      }
      
      
      public function onFaultReportsGen(evt:FaultEvent):void {
        trace("onFaultReportsGen():" + evt);
      }
      
      
    ]]>
  </fx:Script>
  
  <!-- UI部 -->
  <s:VGroup width="100%" height="100%" horizontalAlign="center" paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10">
    <s:HGroup width="100%" verticalAlign="middle">
      <s:Label text="code" width="125" fontSize="18"/>
      <s:Label width="100%" text="{code}"/>
    </s:HGroup>
    <s:TextArea id="taAccessToken" width="100%" height="100%" editable="false"/>
    <s:HGroup width="100%" verticalAlign="middle">
      <s:Label text="accessToken" width="125" fontSize="18"/>
      <s:Label width="100%" text="{accessToken}"/>
    </s:HGroup>
    <s:Button  enabled="{accessToken == null}" label="Start OAuth2" click="onClickBtnStartOAuth2();"/>
    <s:TextArea id="taReportsGen" width="100%" height="100%" editable="false"/>
    <s:Button enabled="{accessToken != null}" label="Request reports.generate" click="requestReportsGen();"/>
  </s:VGroup>
  
</s:WindowedApplication>

以上、終了!うぃーあうちょ〜!