Android アプリでZaimのOAuth 認証を行う


# Zaim開発センターへ登録
[Zaim開発センター](https://dev.zaim.net/users/login)へログインし、OAuth認証したいアプリケーションを登録する。
登録後、下記情報が表示される。認証の際に使用するので記憶しておく。
- Consumer Key
- Consumer Secret
- Request token URL
- Authorize URL
- Access token URL

# oauth-signpostをimportする
今回はOAuth認証のライブラリに[oauth-signpost](https://code.google.com/p/oauth-signpost/)を使用する。
build.gradleに下記記述を追加し、ライブラリを使用できるようにする。

```groovy:build.gradle
dependencies {
    // 以下を追加
    compile 'oauth.signpost:signpost-core:1.2'
    compile 'oauth.signpost:signpost-commonshttp4:1.2'
}
```

# oauth-signpostを使用したOAuth認証手順概要
1. OAuth認証に使用するオブジェクトの生成
2. 認証ページのURLを取得する
3. 認証ページを表示する
4. 認証結果からアクセストークンを取得する
5. 取得したアクセストークンを用いて通信を行う

# OAuth認証を行う
## OAuth認証に使用するオブジェクトの生成
ここで、Zaim開発センターで登録した際に取得できる情報を入力する。

```java:LoginActivity.java
private static final String CONSUMER_KEY = "<<Zaim開発センター登録時の情報>>";
private static final String CONSUMER_SECRET = "<<Zaim開発センター登録時の情報>>";
private static final String REQUEST_TOKEN_URL = "<<Zaim開発センター登録時の情報>>";
private static final String AUTHORIZE_URL = "<<Zaim開発センター登録時の情報>>";
private static final String ACCESS_TOKEN_URL = "<<Zaim開発センター登録時の情報>>";

private OAuthConsumer mConsumer;
private OAuthProvider mProvider;

mConsumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
mProvider = new CommonsHttpOAuthProvider(REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZE_URL);
```

## 認証ページのURLを取得する

OAuthProviderオブジェクトのretrieveRequestTokenメソッドを使用して認証ページのURLを取得する。
retrieveRequestTokenメソッドを呼び出す際に、コールバックのURLを指定するが今回はコールバックは使わないので適当なURLを指定する。

```java:LoginActivity.java
    public interface RequestCallback {
        public void onComplete(String authUrl);
    }

    private class OAuthRequestAsyncTask extends AsyncTask<Void, Void, Void> {

        private RequestCallback mCallback;
        private String mCallbackUrl;
        private String mAuthUrl;

        OAuthRequestAsyncTask(String callbackUrl, RequestCallback callback) {
            mCallbackUrl = callbackUrl;
            mCallback = callback;
        }

        @Override
        protected Void doInBackground(Void... arg0) {
            //OAuth認証
            try {
                mAuthUrl = mProvider.retrieveRequestToken(mConsumer, mCallbackUrl);
            } catch (OAuthMessageSignerException e) {
                e.printStackTrace();
            } catch (OAuthNotAuthorizedException e) {
                e.printStackTrace();
            } catch (OAuthExpectationFailedException e) {
                e.printStackTrace();
            } catch (OAuthCommunicationException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void v) {
            mCallback.onComplete(mAuthUrl);
        }
    }
```

## 認証結果からアクセストークンを取得する

OAuthProviderオブジェクトのretrieveAccessTokenメソッドを使用してアクセストークンを取得する。
retrieveAccessTokenメソッドの引数には、認証後取得できるOAuth Verifierコードを渡す。

```java:LoginActivity.java
    public interface AccessCallback {
        public void onComplete();
    }

    private class OAuthAccessAsyncTask extends AsyncTask<Void, Void, Void> {

        private AccessCallback mCallback;
        private String mOAuthVerifier;

        OAuthAccessAsyncTask(String oauthVerifier, AccessCallback callback) {
            mOAuthVerifier = oauthVerifier;
            mCallback = callback;
        }

        @Override
        protected Void doInBackground(Void... arg0) {
            try {
                // AccessToken取得
                mProvider.retrieveAccessToken(mConsumer, mOAuthVerifier);

            } catch (OAuthMessageSignerException e) {
                e.printStackTrace();
            } catch (OAuthNotAuthorizedException e) {
                e.printStackTrace();
            } catch (OAuthExpectationFailedException e) {
                e.printStackTrace();
            } catch (OAuthCommunicationException e) {
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void v) {
            mCallback.onComplete();
        }
    }
```

# Login画面を作る

ネットアクセスの権限を付ける。

```xml:AndroidManifest.xml
    <uses-permission android:name="android.permission.INTERNET" />
```

Login画面にはWebViewを配置する。
このWebViewにログインページを表示し、この画面を通してログインを行う。

```xml:activity_login.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".LoginActivity">

    <WebView
        android:id="@+id/web_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>
```

認証後のページからOAuth Verifierコードを取得するためのWebViewのsetWebViewClientメソッドを使ってJavaScriptコードを埋め込んでいる。
アラートで抜き出したコードをWebChromeClientのonJsAlertのコールバックを利用して取得する。

```java:LoginActivity.java

        final WebView webView = (WebView) findViewById(R.id.web_view);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                Log.v(this.getClass().getName(), "onPageFinished url = " + url);

                if (url.equals(LOGIN_COMPLETE_URL)) {
                    // 認証が完了したら、ページ内からOAuth Verifierコードを抜き出してアラートとして表示する
                    webView.loadUrl("javascript:window.alert(document.getElementsByTagName(\'code\')[0].innerHTML);");
                }
            }
        });
        webView.setWebChromeClient(new WebChromeClient() {
            @Override
            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                Log.d(TAG, "url = " + url);
                Log.d(TAG, "message = " + message);

                // 上のonPageFinishedで埋め込んだjavascriptでOAuth Verifierのコードが取得できる。
                // このコードを利用してAccessTokenを取得する。
                String oauthVerifier = message;
                OAuthAccessAsyncTask asyncTask = new OAuthAccessAsyncTask(oauthVerifier, new AccessCallback() {
                    @Override
                    public void onComplete() {
                        // OAuthConsumerからTokenとTokenSecretが取得できる
                        Log.d(TAG, "ACCESS_TOKEN : " + mConsumer.getToken());
                        Log.d(TAG, "ACCESS_TOKEN_SECRET : " + mConsumer.getTokenSecret());
                    }
                });
                asyncTask.execute();

                return true;
            }
        });

        // 認証ページのURLを取得する
        OAuthRequestAsyncTask asyncTask = new OAuthRequestAsyncTask(CALLBACK, new RequestCallback() {
            @Override
            public void onComplete(String authUrl) {
                Log.d(TAG, "authUrl = " + authUrl);
                
                // 取得したURLを表示する
                webView.loadUrl(authUrl);
            }
        });
        asyncTask.execute();
```

# 認証後の通信
ユーザ情報を取得するAPIのHTTP GETリクエストを作成するには下記のように、送信する前に署名を行う。

```java
    HttpClient httpClient = new DefaultHttpClient();
    String urlString = "https://api.zaim.net/v2/home/user/verify";
    
    HttpGet httpGet = new HttpGet(urlString);
    // HTTP GETリクエストを署名する
    mConsumer.sign(httpGet);

    // HTTP GETリクエストを送信
    ...
```

# サンプルコード
https://github.com/waura/ZaimOAuthSample