App Engine 1.3.4 のOpenID 認証

月刊 App Engine SDK の6月号は1.3.4。Google I/Oではfor business とか、VMware との協業とか、mapper APIとかchannel APIとかもっと面白い話しがあったようだけど、SDK 1.3.4の最大の売りは、OAuth対応とOpenID対応の二つのユーザ認証機構。ここではOpenIDでの認証のしかたを書いてみる。

リクエストを送るには3rd partyのライブラリかなんかが必要なんだろうと思って、いろいろ調べたり試したりして、数時間を無駄にしたのだけど結局わからず、twitterでつぶやいたら @shin1ogawa さんに1分後に教えていただいた。

ありがとうございました@shin1ogawaさん!そして、おそるべしtwitter。。。

OpenID の動作

OpenID はFederated Identity と呼ばれるものの一つで、個々のサービスにユーザ名とパスワードを登録することなく、ログインできるようにする仕掛け。下記サイトに動作の概要の絵があるのでわかりやすい。

http://www.openid.ne.jp/

ユーザがOpenID対応サービスにログインする際に、OpenIDアイデンティティを入力する。OpenIDアイデンティティはURLの形になっている。ブラウザはOpenID認証者にリダイレクトされ、そこで認証を受けると、元のURLにリダイレクトされる。

OpenIDの特徴は、ユーザがOpenIDの発行団体を選択できること。さらには、IDのURLに自分のドメインを選ぶことができること。

OpenIDはいろいろなサイトで提供している。ここはてなでも提供されていて、

http://www.hatena.ne.jp/はてな名

で利用できる。

App Engineでの使い方

App Engine のAPIは既存のGoogle アカウントを使ったユーザ認証機構と一体化している。ただし、Googleアカウントのほうはプログラマは何もしなくても使えたのに対して、OpenIDのほうは一手間かかる。やらなければいけないのは次の3点

  • 管理コンソールのAplication Settings でAuthentication OptionsをFederated Login に切り替える
  • /_ah/login_required にログイン画面を作る
  • ログインハンドラを書いて、リダイレクトする

ログイン画面

ログインしていないユーザがsecurity-constraints でガードされているページを参照すると、自動的に/_ah/login_requiredにリダイレクトされるので、ここにログイン画面を作る。このとき、ユーザがアクセスした元のアドレスがパラメータ ’continue’ でわたってくる。これを次のログインハンドラまで引き渡さなければならないので、ログイン画面は 単なるHTMLというわけにはいかない。こんなフォームを生成する。ここではhiddenを使って次の画面に元アドレスを引き継いでいる

  <form action="/openIDLoginHandler" method="post">
    <input type="hidden" name="continue" value="URL" />
    <input type="text" name="openid_identifier" />
    <input type="submit" value="Submit" />
  </form>

ログインハンドラ

ログインハンドラはこんな感じ。userService のcreateLoginURLが拡張されて、openID 用になっている。ここで、ユーザが入力したOpenIDのIDを入れてやる。

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        String continuePage = req.getParameter("continue");
        String openidIdentifier = req.getParameter("openid_identifier");

        String url = continuePage;
        String authDomain = url.substring(0, url.lastIndexOf("/"));
        Set<String> attributesRequest = new HashSet<String>();
        
        UserService userService = UserServiceFactory.getUserService();
        String createdUrl = 
               userService.createLoginURL(continuePage, authDomain,
                            openidIdentifier, attributesRequest);

        resp.sendRedirect(createdUrl);
    }      

@shin1ogawa さんに教えていただいたコードではattributesRequestにopenid.mode などをセットしている。たぶんそちらのほうが本当は正しいのだと思うけど、とりあえずattributesRequst が空でも動くには動いた。ためしたのははてなのIDだけなので、他でやったら動かないかも。

所感

Googleアカウントの場合と比べると、かなり面倒だ。ログインページをユーザに任せているのは、ここで示したようないい加減なフォームではなくて、もっと気の利いたインターフェイスを用意しろということなのだろうけど、できればデフォルトのログインページを用意して欲しかったなあ。みんなが似たようなハンドラを書くのももったいないし。