2011年4月10日日曜日

外部認証を使ったアプリケーション④ Google OpenID API サンプル(A 前編)

Google App Engine (GAE)上で提供されるようになった OpenID API について、前回の記事 で解説した。

今回は少し実用的なサンプルを作りながら、気になった点など書いていきたい。

OpenID を利用した“ちょこっと”実用的なサンプルの概要

OpenID を使って認証したユーザが、自分の自己紹介文を登録するアプリケーションを考えてみる。
とりあえず、OpenID認証用と自己紹介文登録用の機能を用意すればよいのがわかる。(下図)

前回の記事の最後にも書いたが、OpenID プロバイダによって認証の仕方には違いがある。OpenID を入力する方式を採用しているプロバイダに対しては、ID入力用の機能を用意する必要がある。このため、ID入力が付いた認証機能を付け足す(下図)。

もちろん OpenID入力方式を採用しているプロバイダを認証から外すのであれば、この機能は必要無い。
これらの各機能(モジュール)について、コードを作成しながら説明していきたい

「OpenID認証」モジュール

OpenID認証モジュールは、認証が必要になった時点でコールされる。前回の記事でも説明したが、GAEでは app.yaml のURLパターン・スクリプトハンドラの下に login: required と記述すれば認証プログラムをコールする。この時コールされる認証プログラムが 今回のOpenID認証モジュールになる。

OpenID認証は、create_login_url関数が吐き出すURLにアクセスすることにより行う。Googleのサンプルプログラムではcreate_login_url関数が出力したURLをHTMLページに出力することに認証を行っていた。この方法は簡単で良いのだが、HTMLページのデザインがどうしても定形のものになってしまう。今回はデザインの変更が容易になるように、HTMLページからのパラメーターでcreate_login_url関数を呼び出し、関数が出力したURLでリダイレクトを行う方法でコーディングを行う(下図)。

認証画面のデザインはユーザに判りやすいように、アイコンを組み合わせたものにする(下イメージ)。

それでは上記の仕様に従って作ったソースを示していきたい。まず OpenID認証モジュールのテンプレートである。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Select login</title>
</head>
<body>
<table    cellpadding="5">
<tr><td><form method="post" action="{% url SelectLoginHandler %}">
    <input type="hidden" name="cont" value="{{ cont|escape }}">
    <input type="hidden" name="url" value="google.com/accounts/o8/id">
    <input type="image" name="button" src="/images/google.png" width="36" hegiht="36" alt="Google"> Googleでログイン
</form></td></tr>
<tr><td><form method="post" action="{% url SelectLoginHandler %}">
    <input type="hidden" name="cont" value="{{ cont|escape }}">    
    <input type="hidden" name="url" value="aol.com">
    <input type="image" name="button" src="/images/aol.png" width="36" hegiht="36" alt="AOL"> AOLでログイン
</form></td></tr>
<tr><td><form method="post" action="{% url SelectLoginHandler %}">
    <input type="hidden" name="cont" value="{{ cont|escape }}">    
    <input type="hidden" name="url" value="myspace.com">
    <input type="image" name="button" src="/images/myspace.png" width="36" hegiht="36" alt="MySpace"> MySpaceでログイン
</form></td></tr>
<tr><td><form method="post" action="{% url SelectLoginHandler %}">
    <input type="hidden" name="cont" value="{{ cont|escape }}">    
    <input type="hidden" name="url" value="yahoo.co.jp">
    <input type="image" name="button" src="http://i.yimg.jp/images/login/btn/btnXSYid.gif" width="241" hegiht="28" alt="yahoo!Japan">
</form></td></tr>
<tr><td><form method="post" action="{% url SelectLoginHandler %}">
    <input type="hidden" name="cont" value="{{ cont|escape }}">    
    <input type="hidden" name="url" value="mixi.jp">
    <input type="image" name="button" src="http://developer.mixi.co.jp/wp-content/uploads/2010/01/login_btn002.gif" alt="Mixi">
</form></td></tr>
<tr><td>
    <a href="{% url InputOpenidHandler %}?cont={{ cont|escape }}&provider=hatena" target="_self">
    <img src="/images/oi_hatena.gif" alt="Hatena"></a>
</td></tr>
</form></td></tr>
</table>
</form>    
</body>
</html>
ソース1 解説
  • GoogleからMixiまでのアイコン(ボタン)には、const 及び url という2つのパラメーターが設定されている(例 10・11行目)。これらはクリックするとPost関数に渡され、関数内で create_login_url のパラメータになる。
  • 「はてな」は ID入力モジュール に渡す必要があるため、アンカーリンクでリンク先を ID入力モジュール に指定している(35・36行目)。
  • テンプレート内で使用しているアイコンは、Yahoo!Japan mixi などは自社で提供している。他のプロバイダ分は静的ファイルとして Vector Social Media Icons をアップロードして利用した。「はてな」については akahoshitakuya.com から拝借した。
参考
Vector Social Media Icons
Google App Engine - 静的ファイルの使用
Yahoo! JAPAN ID ログインボタン
mixi Platform用素材利用ガイドライン
akahoshitakuya.com - 読書メーターがOpenIDに対応しました!(mixi Yahoo はてな)

次に、OpenID認証モジュールのプログラムである。

# -*- coding: utf-8 -*-
import os
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app
from base_handler import BaseHandler
from input_openid import InputOpenidHandler

class SelectLoginHandler(BaseHandler):
    def get(self):
        param = {'cont': self.request.get('continue')}    # 'continue' is api parameter
        template_values = self.make_parameter_dict(**param)
        path = os.path.join(os.path.dirname(__file__), 'templates', 'select_login.html')
        self.response.out.write(template.render(path, template_values))

    def post(self):
        cont = self.request.get('cont')
        url = self.request.get('url')
        login_url = users.create_login_url(dest_url = cont, federated_identity = url)
        self.redirect(login_url)

def main():
    url_map = [('/_ah/login_required', SelectLoginHandler),
               ('/login/input_openid', InputOpenidHandler)]
    application = webapp.WSGIApplication(url_map, debug=True)
    run_wsgi_app(application)

if __name__ == '__main__':
    main()
ソース2 解説
  • Get関数でテンプレートファイルを書き出し、Post関数で選択したプロバイダのURLを create_login_url で生成しリダイレクトする(20・21行目)。
  • Get関数内で continue 変数を取り出しているが、これは認証要求時に AppEngine がセットする元画面のURLが入った変数である(12行目)。continue という名前のままだと、Pythonの予約語に重なるため cont に名前を変更している。
  • BaseHandlerというクラスを使用している。これは今回セッションを使用していないので、Get及びPost変数を受け渡す関数を定義しているクラスだ(7・10行目)。
  • main関数でURLパターンを設定している。/_ah/login_required は AppEngineで定義している認証用のURLのため、これに認証用クラスを割り当てる必要がある(24行目)。他に ID入力モジュール のURLパターンもここで定義している(25行目)。


あまりにも長くなったので、後編(中編?)に続きます。