Androidアプリとサーバとの間で非同期通信(ユーザ認証+ログイン+ユーザ情報更新)

AndroidアプリからRailsサーバ(devise)に対してユーザ登録とログインが必要になるので、Androidの非同期通信を試してみます。

調べてみるとAndroid Asynchronous Http Clientというのが、わりと簡単に使えるらしいです。






参考サイト
Android Asynchronous Http Clientを使ってみた


Android Asynchronous Http Clientの簡単なテスト


新しいプロジェクトを作成

プロジェクト名をHttpClientTest。
Target SDKとCompile WithをAPI16。
あとは適当。

Android Asynchronous Http Clientからandroid-async-http-1.4.2.jarをダウンロードして、libsに投げ込んでConfigure Build Pathからjarを追加。

MainActivity.javaに
import com.loopj.android.http.*;

onCreateのところに
AsyncHttpClient client = new AsyncHttpClient();
client.get("http://www.google.com", new AsyncHttpResponseHandler() {
    @Override
    public void onSuccess(String response) {
        System.out.println(response);
    }
});

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

これで一旦テストしてみる。



LogCatに出てくるメッセージ
<!doctype html><html itemscope="itemscope" itemtype="http://schema.org/WebPage">
<head><meta content="世界中のあらゆる情報を検索するためのツールを提供しています。さまざまな検索機能を活用して、お探しの情報を見つけてください。" name="description">
<meta content="noodp" name="robots"><meta itemprop="image" content="/images/google_favicon_128.png"><title>Google</title><script>(function(){...


とうことで、Googleのページをgetできました。簡単なテスト成功!


Android Asynchronous Http Clientでユーザ登録


RailsのDeviseを使ってると仮定して、
ユーザ登録はこんな感じになります。(一応動きますが、修正箇所があります)

AsyncHttpClient client = new AsyncHttpClient();
client.get("https://example.com/users/sign_up.json", new JsonHttpResponseHandler() {
    @Override
    public void onSuccess(JSONObject json) {
    	try {
    		String token = json.getString("token");
		    		
    		RequestParams params = new RequestParams();
    		params.put("user[username]", "hogeuser");
    		params.put("user[password]", "hogepass");		    		
    		params.put("user[password_confirmation]", "hogepass");
    		params.put("authenticity_token", token);

    		AsyncHttpClient client2 = new AsyncHttpClient();
    		client2.addHeader("Accept", "application/json");
    		client2.post("https://example.com/users", params, new JsonHttpResponseHandler() {
    		    @Override
    		    public void onSuccess(JSONObject json2) {
			        System.out.println(json2);
    		    }
    		});

    	} catch (JSONException e) {
	        System.out.println("error");
    	}
    }
});


RailsのDeviseでのユーザー登録で引っかかるのが、authenticity_token (CSRF token)です。

CSRF tokenを送らないと、
WARNING: Can’t verify CSRF token authenticity
とかいう気持ち悪い警告がログに出てきます。

iOSの場合、Rails側のapplication_controllerのbefore_filterのところに、
response.headers[‘X-Authenticity-Token’] = form_authenticity_token
とか何とか入れてあげると、headerと一緒にtokenが送られてきます。

iOSのプログラムで
[[request responseHeaders] objectForKey:@”X-Authenticity-Token”];
とか入れてあげると、tokenをゲットすることが出来ます。

が、Android Asynchronous Http Clientの場合、
このheaderが取れない取れない。。

request responseHeadersみたいなのがないみたい。。


いろいろ試してのheaderからは簡単に取れそうもないので、
getのリターン時のjsonでtokenをもらうことにしました。

class Users::RegistrationsController < Devise::RegistrationsController を少し変更。



https://example.com/users/sign_up.json
をgetリクエストすると、
String token = json.getString(“token”)でtokenをもらえます。

それをユーザー登録するときのpostと一緒に送って上げる。。


ユーザ登録はできたけど、なぜかRailsのlogに下記の警告が出てくる・・・。
WARNING: Can’t verify CSRF token authenticity


Android Asynchronous Http Clientでログイン


WARNINGが出てくるけど、とにかくデータベース的にはユーザが登録されているので次に進んでしまいます。
で、ここでひどく悩みました。


Androidアプリ側のコード

まずログインしているかどうか調べて、
ログインしていない場合、handleFailureMessageで捕まえて、
トークンをもらって、ログインして、ユーザ情報を更新する。

というプログラム。
でもユーザ情報が更新できません。

AsyncHttpClient client = new AsyncHttpClient();
		
client.get("https://example.com/users/loginCheck.json", new JsonHttpResponseHandler() {
    @Override
    public void onSuccess(JSONObject json) {
	System.out.println("onSuccess");
        System.out.println(json);
    }

    @Override
    public void handleFailureMessage(Throwable e, String responseBody) {
	System.out.println("handleFailureMessage");
        System.out.println(e);
		        
        AsyncHttpClient tokenClient = new AsyncHttpClient();
        tokenClient.get("https://example.com/users/sign_up.json", new JsonHttpResponseHandler() {
        	@Override
    		public void onSuccess(JSONObject json) {
        		try {
        			final String token = json.getString("token");
		            		
        			RequestParams params = new RequestParams();
	            		params.put("user[username]", "hogeuser");
        			params.put("user[password]", "hogepass");		    		
	            		params.put("authenticity_token", token);
	            		params.put("user[remember_me]", "1");

	               		AsyncHttpClient loginClient = new AsyncHttpClient();
	               		loginClient.addHeader("Accept", "application/json");
	               		loginClient.post("https://example.com/users/sign_in.json", params, new JsonHttpResponseHandler() {
		               		public void onSuccess(JSONObject json) {
						System.out.println("login");
				        	System.out.println(json);
				        			
				        	AsyncHttpClient profileClient = new AsyncHttpClient();

				        	RequestParams params2 = new RequestParams();
				        	params2.put("profile", "I am the God!!!");		    		
				            	params2.put("authenticity_token", token);
				            		
				            	profileClient.addHeader("Accept", "application/json");
				            		client.post("https://example.com/users/update_profile.json", params2, new JsonHttpResponseHandler() {
				               		public void onSuccess(JSONObject json) {
						        	System.out.println("update_profile");
						        	System.out.println(json);
		               				}
		               			});
               				}
               			});
        		} catch (JSONException e) {
        			System.out.println("error");
        		}
        	}
        });
    }
});


Railsのログを見ていると、

ログインしたときも
WARNING: Can’t verify CSRF token authenticity
というログが出ています。でもログインはしている感じです。

でも、その後すぐにユーザ情報を更新しようとすると、
認証に失敗して更新できないというログが出てきて、更新に失敗します。

ちなみに、ユーザ情報はログインユーザのみが変えられます。


CSRF tokenがおかしいのは確かなんですが、
なんでおかしいのかわかりません。

iOSアプリの場合、同じような手続きで問題なく更新してました。。


で、かなり時間を使っていろいろ試してみると・・、原因が判明!

全ての原因は
AsyncHttpClient client = new AsyncHttpClient();
AsyncHttpClient loginClient = new AsyncHttpClient();
AsyncHttpClient profileClient = new AsyncHttpClient();

このコードが原因でした。

clientはどうでもいいんですが、
loginClientがtokenをゲットして、そのtokenをprofileClientが使おうとすると、
人が違う!ということでRails側がtokenを拒否していました。


その結果、無効なtokenを使ってきたので、
WARNING: Can’t verify CSRF token authenticity
というメッセージがログに出て、ログインしても直ぐにログアウトされてしまっているようです。

profileClientは作成せずに、loginClientの作成時にfinalを付けます。
final AsyncHttpClient loginClient = new AsyncHttpClient();

そして引き続きloginClientでユーザ情報を更新するとtokenを受け付けて更新に成功しました。


今回の教訓

AsyncHttpClient client = new AsyncHttpClient();
AsyncHttpClient client2 = new AsyncHttpClient();

clientとclient2は別人なので、同じtokenは使えません。

コメントを残す

メールアドレスが公開されることはありません。