Android アプリで OAuth 認証する


Android（というかJava）で OAuth 認証を実装してみたので、手順をメモしました。
ここでは OAuth1.a での認証を想定してます。


## 1. OAuthライブラリをダウンロードする

OAuth とりわけ1.aは手順が複雑なので、ライブラリを使ったほうが楽です。
Java で使えるライブラリは、以下のようなものがあるみたいです。

* [oauth-signpost]("http://code.google.com/p/oauth-signpost/")
* [scribe-java]("https://github.com/fernandezpablo85/scribe-java")

※以降は oauth-signpost の実装例です。

上記URLから次の2つをダウンロードします。
（下記バージョンは、2013/10/17時点での最新です。）

* signpost-core-1.2.1.2.jar
* signpost-commonshttp4-1.2.1.2.jar

これらを Android プロジェクトに import します。

## 2. 認証するサービスの開発者登録をする

OAuth 認証するためには、ConsumerKey と ConsumerSecret の情報が必要です。
それらを取得するために、OAuth認証をするサービスの開発者サイトで、開発者登録をします。すると上記2つの情報に加え、アクセス先URLなどの情報も得ることが出来ます。

この後必要になるのは、主に以下の情報です。

* Consumer key
* Consumer secret
* Request token URL
* Authorize URL
* Access token URL
* Callback URL

## 3. ブラウザでユーザーに OAuth 認証してもらうコード

ユーザーのアカウント情報で OAuth 認証してもらうにあたり、ブラウザで認証画面を表示させるようにします。（ログイン画面を作る手間がかからないので。）
以下は該当する箇所のみ、抜粋したものです。

```java:MainActivity.java
public class MainActivity extends Activity {

    private static final String CONSUMER_KEY = "xxxx";
    private static final String CONSUMER_SECRET = "xxxx";
    private static final String REQUEST_TOKEN_URL = "xxxx";
    private static final String AUTHORIZE_URL = "xxxx";
    private static final String ACCESS_TOKEN_URL = "xxxx";
    private static final String CALLBACK = "xxxx";

    private OAuthConsumer mConsumer;
    private OAuthProvider mProvider;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Oauth認証を開始
        OAuthRequestAsyncTask oAuthAsyncTask = new OAuthRequestAsyncTask();
        oAuthAsyncTask.execute();
    }

    /**
     * OAuth認証の通信を行うクラス
     */
    private class OAuthRequestAsyncTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void...arg0) {
            // Oauth認証
            try {
                mConsumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
                mProvider = new CommonsHttpOAuthProvider(REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZE_URL);
                String authUrl = mProvider.retrieveRequestToken(mConsumer, CALLBACK);
                // ブラウザに認証ページを開かせる
                MainActivity.this.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)));
            } catch (OAuthMessageSignerException e) {
                e.printStackTrace();
            } catch (OAuthNotAuthorizedException e) {
                e.printStackTrace();
            } catch (OAuthExpectationFailedException e) {
                e.printStackTrace();
            } catch (OAuthCommunicationException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
```

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

まずはブラウザからのコールバックを受け取れるように、AndroidManifest.xml に Intent-filter を定義します。

```xml:AndroidManifest.xml
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:launchMode="singleInstance" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="xxxx" android:host="xxxx" />
            </intent-filter>
        </activity>
```

`<data>` で定義するのは、3 で定義している CALLBACK の URL と同じものを定義します。
例えば、 `myapp://callback` という CALLBACK であれば、 `scheme="myapp"` で `host="callback"` となります。
`launchMode="singleInstance"` は、何個も同じActivityを生成しないようにするための宣言です。

アクセストークンを取得するためのコールバック処理は、以下のようになります。

```java:MainActivity.java
    /**
     * Intentからの起動
     * @param intent
     */
    @Override
    public void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        Uri uri = intent.getData();
        // ブラウザ認証からのコールバック処理
        if (uri != null && uri.toString().startsWith(CALLBACK)) {
            // アクセストークン取得およびリクエスト処理
            OAuthAccessAsyncTask oAuthAccessAsyncTask = new OAuthAccessAsyncTask();
            oAuthAccessAsyncTask.execute(uri);
        }
    }

    /**
     * OAuth認証のコールバック処理を行うクラス
     */
    private class OAuthAccessAsyncTask extends AsyncTask<Uri, Void, Void> {
        @Override
        protected Void doInBackground(Uri...uris) {
            Uri uri = uris[0];
            final String oauthVerifier = uri.getQueryParameter(OAuth.OAUTH_VERIFIER);
            try {
                // AccessToken取得
                mProvider.retrieveAccessToken(mConsumer, oauthVerifier);
                Log.d(TAG, "ACCESS_TOKEN : " + mConsumer.getToken());
                Log.d(TAG, "ACCESS_TOKEN_SECRET : " + mConsumer.getTokenSecret());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
```

上記で Log に出力している AccessToken を使ってリクエストを送信するので、取得後は保存しておくと使い回しがし易いです。

## 5. OAuth認証結果を使ってAPIを呼び出す

APIを実際に呼び出す際には、リクエストデータを以下のように署名してから送信します。

```java:MainActivity.java
        HttpClient httpClient = new DefaultHttpClient();
        String urlString = "xxxx";

        HttpGet httpGet = new HttpGet(urlString);
        // リクエストデータを署名する
        mConsumer.sign(httpGet);

        String responce = httpClient.execute(httpGet,
                new ResponseHandler<String>() {
                    @Override
                    public String handleResponse(HttpResponse httpResponse) throws ClientProtocolException, IOException {
                        // HttpStatus.SC_OK (HTTP200)
                        if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                            return EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
                        }
                        return null;
                    }
                });
        Log.d(TAG, "responce : " + responce);
```