2011年4月25日月曜日

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

今回は OpenID API を使ったサンプルの最終回です。

アプリケーション(自己紹介登録モジュール)

前編 及び 中編OpenID認証OpenID認証(ID入力) を例示した。今回これらのモジュールを使った自己紹介登録アプリケーションのサンプルを示していきたい。


自己紹介登録モジュールは、ユーザ毎に名前と自己紹介文を登録・表示を行う。極めて単純なモジュールだ。

自己紹介登録モジュールの処理

  1. User クラスからユーザ情報を取得
  2. ユーザ情報からデータストアを検索し、該当するデータがある場合表示する
  3. 登録ボタンを押すと、画面入力した名前と自己紹介文をデータストアに格納

このモジュールの画面は下のようなイメージになる。


それではソースを示していきたい。自己紹介登録モジュールを構成するのは以下のソースである。

  • モデル - models.py
  • テンプレート - main.html
  • プログラム - main.py

# -*- coding: utf-8 -*-
from google.appengine.ext import db

class UserData(db.Model):
    user = db.StringProperty(required=True)
    name = db.StringProperty(required=True)
    comment = db.TextProperty(required=True)
    updated = db.DateTimeProperty(auto_now=True)
    created = db.DateTimeProperty(auto_now_add=True)
ソース7 解説
  • UserData クラスが、データストアでのエンティティの定義である。
  • user にはユーザIDが入る。Googleアカウント認証の場合は UserProperty を利用するが、OpenID(Federated Login)では UserProperty を使うと動かない。この件は後で説明する。
  • name 及び comment は名前とコメントが入るプロパティである。
  • updated 及び created は日時が入るプロパティである。オプションが設定してあり、created の方はエンティティを追加した日時、updated はエンティティの更新日時が自動で設定される。

<!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>☆自己紹介☆</title>
</head>
<body>
<form method="post" action="">
<table cellpadding=5>
<tr><td colspan=2>お名前と自己紹介を登録してください。</td>
<td><a href="{{ logout_url }}" target="_self">ログアウト</a></td></tr>
<tr><td>お名前</td>
<td><input type="text" name="name" size=40 value="{{ name|escape }}"></td></tr>
<tr><td>自己紹介</td>
<td><textarea  rows="5" cols="40" name="comment">{{ comment|escape }}</textarea></td></tr>
<tr><td>更新日時</td>
<td>{{ updated }}</td></tr>
<tr><td></td>
<td><input type="submit" value="登録"/></td></tr>
</table>
</form>
</body>
</html>
ソース8 解説
  • Submitボタンを押すと、Post関数が呼ばれる。

# -*- coding: utf-8 -*-
import os
from google.appengine.ext.webapp import template
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.api import users
from google.appengine.ext import db
from models import UserData

class EditHandler(webapp.RequestHandler):
    def get_user_data(self):
        self.current_user = users.get_current_user().user_id()
        query = db.Query(UserData)
        query.filter('user = ', self.current_user)
        self.user_data = query.get()

    def get(self):
        self.get_user_data()
        if self.user_data is not None:
            name = self.user_data.name
            comment = self.user_data.comment
            updated = self.user_data.updated
        else:
            name = ''
            comment = ''
            updated = ''
        template_values = {'logout_url': users.create_logout_url('/'),
                           'name': name, 'comment': comment, 'updated': updated }
        path = os.path.join(os.path.dirname(__file__), 'templates', 'main.html')
        self.response.out.write(template.render(path, template_values))
    
    def post(self):
        name = self.request.get('name')
        comment = self.request.get('comment')
        self.get_user_data()
        if self.user_data is not None:
            self.user_data.name = name
            self.user_data.comment = comment
            self.user_data.put()
        else:
            UserData(user=self.current_user, name=name, comment=comment).put()
        self.redirect('/')
        
def main():
    run_wsgi_app(webapp.WSGIApplication([('/', EditHandler),], debug=True))

if __name__ == '__main__':
    main()
ソース9 解説
  • Get関数では、認証情報からデータストアを検索した結果の表示を行っている(17行目)。
  • Post関数では、画面入力した情報をデータストアに登録・更新している(32行目)。
  • Get関数・Post関数とも get_user_data を呼びだしているが、get_user_data ではユーザ・インスタンスの取得及びデータストアの検索を行っている(11行目)。
  • ユーザの識別には、ユーザインスタンスの user_id() メッソドを利用している(12行目)。これについても後で解説する。
以上で、自己紹介登録モジュールについては終わる。次に環境設定について簡単に書いてみる。

アプリケーションの環境設定

まず URLパターンであるが、app.yaml 内に記述する URLハンドラー記述は次のようになる。

handlers:
- url: /images
  static_dir: media/images

- url: /css
  static_files: media/css
  upload: media/css/.+\.htc
  mime_type: text/x-component

- url: /css
  static_dir: media/css

- url: /_ah/login_required
  script: select_login.py

- url: /login/.*
  script: select_login.py

- url: .*
  script: main.py
  login: required
ソース10 解説
  • /_ah/login_required (13行目)は 以前の記事 で解説した Federated Login にした場合に使用する URL である。ここに認証用のモジュールを割り当てる。
  • /login/.* (16行目)は、OpenID認証(ID入力)で利用しているが、パターンは select_login.py の方で定義されている。
  • .* (19行目)はアプリケーションの定義である。ここで login: required オプションを付けているため、ユーザ認証が動く。

次に開発環境における各ファイルの配置であるが、下のようになる。

OpenID認証モジュール では各種アイコンを使っている(前編)。ファイルは media\images に配置する。
参考
Vector Social Media Icons
akahoshitakuya.com - 読書メーターがOpenIDに対応しました!(mixi Yahoo はてな)

OpenID認証(ID入力)モジュール ではIE用に PIE.htc を使用している(中編)。このファイルは、media\css に配置する。デザインだけの問題なので、ファイルを配置しなくても問題は無い。
参考: CSS3 PIE

フォルダー構成を変更したい場合は、 app.yaml の記述を変更すればよい。

サンプル作成で気づいた点・留意点など

箇条書きに、気づいた点・留意点などを書いていく。

セッションの使用
今回セッションを使用していない。必要な変数は Get・Post変数として渡しているが、これはスマートなやり方ではない。また特に、Get変数は改ざんが容易である。サンプルではプロバイダの検証を行っていないため、リストに載っていないブロバイダでもログインできる。やはりこのようなアプリケーションではセッションの利用をお勧めする。
 
OpenID認証(ID入力)の入力方式
サンプルでは汎用性を持たせるため OpenID を全て入力する形式にした(中編)。ガイダンスを表示するにしても、この方法でユーザに入力してもらうにはハードルが高い。「はてな」などは、OpenIDの一部分にプロバイダーのユーザIDを使っている。そのようなプロバイダーの場合は、ユーザIDの入力画面だけ用意して、プログラムで OpenID(URL)を生成した方が安全で快適だ。
 
ユーザID を格納するプロパティ
ユーザID をデータストアに格納するプロパティとして、文字列(StringProperty)を利用している(ソース7)。Googleアカウントの場合は UserProperty を使うが、OpenID(Federated Login)では利用できない。もし UserProperty で使った場合、このような現象 に悩まされることになる。
 
ユーザID(Userインスタンス)
データストアに格納するユーザID として、Userインスタンスの user_idメソッドを利用している(ソース9)。永続的で不変な ID を返すとされている。しかしこの ID は、どこのプロバイダのIDなのかも判別不能だ。サンプルでは行っていないがデータストアにユーザ情報を格納するときに、プロバイダ情報などを一緒に入れた方がよい。
user_idメソッドを使わずに、Userインスタンスの値を使ってデータストアに格納してもよいかもしれない。Userインスタンスが返す値は、Googleの場合は GoogleID、他のプロバイダは OpenID となっているようだ。
参考: Google Code - Userクラス
 
メールアドレス・ユーザ名など
Google Code には Userクラスに、メールアドレス・ニックネームなどのメソッドの紹介がある(Google Code - Userクラス)。これらのメッソドが値を返すのは、プロバイダが Google の場合だけだ。OpenID(Federated Login)API は現在、AX プロトコルなどに対応していない。
 
Facebook, twitter, WindowsLive アカウント
Facebook, twitter, WindowsLive は OpenIDに対応しているとアナウンスしているが、OpenID のプロバイダ・サービスは行っていないようだ。これらのアカウントを使って OpenID認証対応アプリケーションにアクセスすることはできない。
 

長々と記事を書いてしまい、申し訳ないです。もっとサクっと紹介できるかと思ったのですが・・・難しかったです。今後は、もうちょっと軽い内容の記事を心がけます。