fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

VolleyでDigest認証をやってみる

この頃やっと仕事でプログラミングやるようになったので折角だしTDDでやってみるかと思ったらEclipseにActivityのコードを自動生成されて出鼻をくじかれました。

そんなことはどうでもよくて、今日はHTTPライブラリVolleyでDigest認証を実装してみたという話。誰得感は否めない。あくまでも私のメモです。

Digest認証のながれ

  1. クライアントからサーバにリクエストを投げると401が返ってくる。
  2. 401と一緒に受け取るWWW-Authenticateヘッダのパラメータrealmやらnonceなどと、usernamepasswordを使ってAuthorizationヘッダを作る。
  3. Authorizationヘッダにメッセージ本文をつけたりして再度サーバにリクエストを投げる。ヘッダのパラメータが正しければ200が返ってくる。

Volley内部の処理のながれ

  1. Volley.newRequestQueue(Context)でリクエストキューを作る。このときBasicNetworkインスタンスが生成され、リクエストの送信に使われるようになる。
  2. リクエストキューにリクエスト(Requestクラスを継承したクラスのインスタンス)を追加するとNetworkDispatchさんが順番にリクエストをさばいてくれる。
  3. リクエストが送られる前にRequest<T>#getHeaders()が呼ばれる。Authorizationヘッダなどを追加したいときはこのメソッドを自前のリクエストクラスでオーバーライドする。
  4. BasicNetworkperformRequestでは401を受け取るとattemptRetryOnExceptionメソッドが呼ばれる。
  5. リクエストにリトライポリシーを仕込んでおくと、指定したリトライ回数だけ同じリクエストを投げてくれる。

つまり、リトライポリシーの中でWWW-Authenticateヘッダを使ってAuthorizationヘッダを作り、次回のリクエストに含めて再度投げてもらえばよいということです。

実装例

いろいろ面倒臭かったのでDefaultRetryPolicyクラスを継承してretryメソッドだけオーバライドした。 Requestクラスを継承したMyRequestクラスの内部クラスです。 retryメソッドはこのままだと認証に失敗し続けると抜け出せないので工夫してください。

Authorizationヘッダを作成するにはDigestSchemeクラスを使うのが便利です。 ざっくりとした使い方はgetHeadersメソッドの中をごらんください。 例外を吐くので適宜キャッチしてください。

あと、authenticateメソッドに渡しているcredencialsにはユーザ名とパスワードが、 requestにはメソッド(GETとかPOSTとか)とURLが含まれています。 それぞれUsernamePasswordCredencialsとかHttpPostとかを参照してください。

public class MyRequest extends Request<JSONObject> {

// いろいろ略
// コンストラクタとかでDigestRetryPolicyを生成する

   @Override
   public Map<String, String> getHeaders() throws AuthFailureError {
      DigestScheme ds = new DigestScheme();
      ds.processChallenge(new BasicHeader("WWW-Authenticate",
                     mChallengeHeader.get("WWW-Authenticate"));
      Header header = ds.authenticate(credencials, request);

      Map<String, String> newHeader
                 = new HashMap<String, String>(super.getHeaders());
      newHeader.put(header.getName(), header.getValue());
      return newHeader;
   }

   private Map<String, String> mChallengeHeader;

   public void setChallengeHeader(Map<String, String> header) {
      this.mChallengeHeader = header;
   }

   class DigestRetryPolicy extends DefaultRetryPolicy {
      @Override
      public void retry(VolleyError error) throws VolleyError {
         if (error.statusCode == HttpStatus.SC_UNAUTHORIZED) {
            setChallengeHeader(error.networkResponse.header);
         } else {
            super.retry(error);
         }
      }
   }

}

こんなんでよいのか

正直良くわからん。いい方法があったら教えて下さい。 あとVolleyライブラリの中にAuthenticatorといういかにもなインタフェースがあってこれを使えるとかっこよさげなのですが、そもそもどういう時にどうやって使えばよいのかわからなかったのでその辺も教えてくださる方をお待ちいたしております。

書いてると正しいかどうか疑心暗鬼になっていく…処理の流れと雰囲気さえ掴んでいただければ幸いです。