2013年1月12日土曜日

Verifying Back-End Calls from Android Apps 翻訳(Androidアプリからのバックエンド認証の呼び出し)

チームEGGの曽川です。

原文はVerifying Back-End Calls from Android Appsです。
Androidアプリとサーバが通信をする際に、誰と何の通信を行っているのか判別する方法です。
この記事では概要が書かれておりますので、実際の実装は各リンクをたどって進める必要がありそうです。
翻訳に誤りや良い表現があればご指摘をお願いいたします。


Verifying Back-End Calls from Android Apps
ほとんどのAndroidアプリは、データの永続化や共有のためにサーバ側のバックエンドのようなものを持っています。
もっとも基本的なゲームは、プレイヤーのハイスコアを覚えておく必要があります。
バックエンドを構築するとき、解決しなければならない1つの問題はどのようにバックエンドのコードが、アプリと何を話しており、誰が使っているのか知ることです。

クライアントアプリと通信するHTTPのエンドポイントを持っていると思いますが、サーバ側のコードがどのようにしてメッセージを送信している人を確認しますか?
結局のところ、誰もがどこからでもHTTPのPOSTリクエストを送信できます。
IDを推測できれば、ユーザを偽装することができます。

端末デバイス上でユーザ名とパスワードを入力をお願いすることは、本当に使い勝手が悪いです。
特に、もし誰かがアプリをインストールし、インターネットを使用して、IDを知るための許可を与えているならば、これ以上悩むことはありません。

GooglePlayサービスは、Googleアカウントの使用を基盤にするよい解決策の提供を開始しています。(Android2.2以上を実行する互換性のあるすべての端末上で現在利用可能です)

概要
複数のステップを完全に説明しますが、ここは短いバージョンです。
IDトークンと呼ばれる文字列を取得するためにお、GooglePlayを通じて利用可能なGoogleAuthUtilクラスを使用します。
バックエンドにトークンを送り、バックエンドはどのアプリから送られたものか誰がアプリを使っているのかを素早く容易に確認するために使用できます。



この機能は、単純なプログラミングモデルにアプリ/バックエンドIDを焼きこむ、App Engineの新しいクラウドエンドポイント機能のようなGoogleの機能に組み込まれています。

それでは、詳細を入手していきましょう。

アプリ登録
GooleAPIコンソールを使用するために多くの手順が必要です。
この目的のために、新しいプロジェクトを作成しなければなりません。
人間が読める形式の名前とグラフィカルブランドを与えることができますが、それらのリソースはこの特別なシナリオでは使用されていません。

また、多くの異なるGoogle APIにアクセスするために、このプロジェクトに権限を与えることができますが、このシナリオを2回行う必要はありません。
これらは重要な管理の役割であるため、プロジェクトのメンバーとして承認する人を真剣に考えなければなりません。

クライアントIDの作成
あなたのプロジェクトに、2つの異なるOAuth2.0の"クライアントID"が必要です。
1つ目は、Webアプリケーションの"クライアントID"です。
改めて、ラベリングやブランディングのものを無視することができ、9414861317621.apps.googleusercontent.comのようになるクライアントIDの文字列のみが必要です。

今、あなたはAndroidアプリのために、別のクライアントIDを作成する必要があるでしょう。
これを行うには、次の2つの情報を提供する必要があります。
アプリパッケージ名、証明書の署名です。
パッケージ名は、Java形式のDNSの逆順(AndroidManifest.xmlの一番上の「package」属性で与えられたもの)です。(例:com.example.identity)

アプリの証明書の署名を取得するには、次のシェルコマンドを実行してください。
$ keytool -exportcert -alias  -keystore  -v -list

"SHA1"とラベルされたオクテットをコピーし、デベロッパーコンソールのフィールドに貼り付けてアプリのクライアントIDを生成してください。
改めて、あなたが読み出したものから本当に必要なものはクライアントID文字列だけです。

Androidアプリ内
IDトークンを取得するために、GooglePlayサービスのGoogleAuthUtilクラスを呼び出す必要があります。
手順はObtaining an Access Tokenで説明されています。
魔法の余分な1ビットがあります。getToken(email, scope)メソッドのscope引数の値です。
文字列は、audience:server:client_id:Xとなり、Xは上で説明したWebアプリのためのクライアントIDです。
クライアントIDが上の例の値で与えられた場合、scope引数の値はaudience:server:client_id:9414861317621.apps.googleusercontent.comとなります。

魔法のようなことが起こります
通常OAuthトークンを要求すると、デバイスを利用する人はいくつかのリソースやその他の取得時にIDの使用してもかまわないかどうかを求める申し込みを見ます。
しかし、この場合システムはscope引数のサーバ側のクライアントIDを見て、あなたのAndroidアプリと同じプロジェクトだとわかると、ユーザを困らせることなくトークンを与えます。
(ユーザはプロジェクトを制御する開発者との関係にすでに同意しています。)

トークンの送信
サーババックエンドに話を開始する準備ができたとき、トークン文字列を送信する必要があります。
これを行うもっともよい方法はPOSTメッセージの本文に含むことです。
(URLパラメータに入れることもできますが、記録されることがあります。)
トークンの覗き見から中間者であることを保つために絶対にHTTPS通信を使用してください

トークンの使用
サーバがAndroidアプリからトークンを受け取るとき、それを確認することは非常に重要です。
2つの手順が必要です。
  1. 本当にGoogleからの署名であること確認します。
  2. 本当にあなたに宛てられたものであるか確認します。

署名の確認
Googleの公開/秘密鍵を使ってこれが署名されていることがわかったら、Googleはwww.googleapis.com/oauth2/v1/certsの公開鍵(定期的に変更する)を発行します。
(見に行ってください。)

実際にJSONウェブトークンであるIDトークンは、それらの証明書のいずれかを使用して署名されたことを確認しなければなりません。
幸い、これらを行うためのきちんとしたライブラリがあります。
(この記事ではJava、Ruby、PHPのための指針を与えます)

ライブラリは、Googleの証明書をキャッシュでき、必要な時にのみリフレッシュをおこなうので、確認は(ほとんどの場合)高速な静的呼び出しです。

トークンフィールドの確認
IDトークンがJSONペイロードを持っているとわかったら、署名を検証する多くのライブラリはハッシュや辞書または何かを与えます。
したがって、このようなaudやCIDやemailのような名前検索が可能となります。

まず、AUDという名前のフィールドを見て、あなたのクライアントID(Androidアプリのscope引数に含まれる文字列)と同一か確認します。
絶対にこの手順を省略してはいけません。
もしIDトークンを確認しなければ、ほかの開発者があなたのサービスへの要求を偽造することができます。

必要に応じて、CISという名前のフィールドを見てあなたのAndroidアプリのクライアントIDと同一か確認してください。
余談ですが、トップレベルプロジェクトそれぞれ独自のクライアントIDで複数の異なったAndroidクライアントアプリを持つことができます。

これらのこと3つを行ったと仮定します。その後、あなたは以下を知っています。

  1. Googleから発行されたトークン
  2. トークンが、ペイロードのemailフィールドで識別される人によって操作されたデバイスに送られました。

また、高い信頼性を持っています。
  1. トークンは、ペイロードのCIDフィールドのクライアントIDによって識別されたAndroidアプリによって得られました。

非互換性やルート化されたAndroidデバイスが情報を改ざんすることができるかもしれないので、クライアントIDは高い信頼性を持っています。
Google署名やGoogleへ、デバイスのユーザー認証の偽装をすることができません。

次は?
あなた次第です。
あなたが話している人とアプリを知って、その情報をどのように処理するかはあなた次第です。

コードの断片
GoogleのJavaライブラリを使用して、IDトークンチェッカーを実装するJavaクラスは次のとおりです。

import java.io.IOException;
import java.security.GeneralSecurityException;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;

public class Checker {

    private final List mClientIDs;
    private final String mAudience;
    private final GoogleIdTokenVerifier mVerifier;
    private final JsonFactory mJFactory;
    private String mProblem = "Verification failed. (Time-out?)";

    public Checker(String[] clientIDs, String audience) {
        mClientIDs = Arrays.asList(clientIDs);
        mAudience = audience;
        NetHttpTransport transport = new NetHttpTransport();
        mJFactory = new GsonFactory();
        mVerifier = new GoogleIdTokenVerifier(transport, mJFactory);
    }

    public GoogleIdToken.Payload check(String tokenString) {
        GoogleIdToken.Payload payload = null;
        try {
            GoogleIdToken token = GoogleIdToken.parse(mJFactory, tokenString);
            if (mVerifier.verify(token)) {
                GoogleIdToken.Payload tempPayload = token.getPayload();
                if (!tempPayload.getAudience().equals(mAudience))
                    mProblem = "Audience mismatch";
                else if (!mClientIDs.contains(tempPayload.getIssuee()))
                    mProblem = "Client ID mismatch";
                else
                    payload = tempPayload;
            }
        } catch (GeneralSecurityException e) {
            mProblem = "Security issue: " + e.getLocalizedMessage();
        } catch (IOException e) {
            mProblem = "Network problem: " + e.getLocalizedMessage();
        }
        return payload;
    }

    public String problem() {
        return mProblem;
    }
}


あなたがRubyでこれをやってみたい場合は、 google-id-token Ruby gemをインストールして、このように書きます

require 'google-id-token'
validator = GoogleIDToken::Validator.new
jwt = validator.check(token, required_audience, required_client_id)
if jwt
  email = jwt['email']
else
  report "Cannot validate: #{validator.problem}"
end

PHPプログラマは、Google APIs Client Library for PHPをチェックアウトしてください。
(apiOAuth2.phpのverifyIdTokenの関数)

0 件のコメント:

コメントを投稿