2.2.7. OpenID¶
OpenID を使った認証方式です。
Note
OpenID は一つのIDで、対応するWebサイトで共通して利用できるIDのことです。IDは URL や XRI 形式で記述します。
設定¶
OpenIDでは python-openid モジュールを使用します。 このため、事前にインストールしておくことが必要です。
- pyton-openid のインストール方法
easy_install python-openidもしくは
pip install python-openid
login_form
に OpenIDAuth
インスタンスを設定します。設定例
from gluon.contrib.login_methods.openid_auth import OpenIDAuth
auth.settings.login_form = OpenIDAuth(auth)
OpenIDAuth
コンストラクタのパラメータには、 Auth インスタンスを指定する必要があります。
OpenIDモジュールは、Google AppEngine(GAE)でも動作可能です。設定は次のページを参照してください。
使用例¶
OpenID は他の認証方式と比べて複雑な動きをします。このためまず、基本的な設定でのシステム動作の説明をします。
基本的なシステム動作¶
from gluon.contrib.login_methods.openid_auth import OpenIDAuth auth.settings.login_form = OpenIDAuth(auth)この設定の場合のシステム動作は次のようになります。
[1] OpenID のサインイン画面
![]()
![]()
[2] プロバイダの認証画面
![]()
![]()
Yahoo Japan の場合
![]()
[3] ユーザ紐付け
![]()
![]()
完了
![]()
![]()
[6] ログアウト
![]()
[5] 紐付け用ユーザ登録
![]()
[1] OpenIDを入力し、サインインします。
[2]
- 入力した OpenIDのプロバイダ認証画面に遷移しますので、ログインします。
- ログインした OpenID は alt_logins テーブルに自動保存されます。
[3]
- auth_user テーブルのユーザIDと OpenID の紐付けを行う必要があります。
- Form認証で紐付けしたいユーザIDを使ってログインすることにより、紐付けが完了します。
- 既に紐付いているユーザIDを使用した場合は、紐付けが失敗します。
- 紐付けしたいユーザが存在しない場合、ユーザ登録で新しいユーザを登録します。
[4] 紐付けが完了し、OpenID を使用しシステムにアクセスできるようになりました。
[5] 新規のユーザを登録します。
[6] ユーザ登録するとシステムにログインしますが、まだ OpenID との紐付けが終わっていません。 ログアウトしてから OpenIDを使ったサインインを行い、紐付けの処理をします。 OpenIDと通常のユーザIDとの、紐付け処理を行う必要があります。このため他の認証方式と比べても、複雑にシステムが動作します。 なお、OpenIDのサインイン画面イメージは次のようになります。
![]()
alt_logins テーブルについて¶
OpenID を認証に使用すると、alt_logins テーブルが自動作成されます。
db.define_table('alt_logins', Field('username', length=512, default=''), Field('type', length =128, default='openid', readable=False), Field('user', self.table_user, readable=False))サインインした OpenID は alt_logins テーブルの username フィールドに格納されます。
user フィールドは auth_user テーブルの id が設定されます。これによって auth_userテーブルとの紐付けが設定されます。
auth_userテーブルとの紐付けが行われると、次回 OpenID でログインした時に auth_user の該当するIDの パスワードフィールドに null値が設定されます。これによって Form認証画面からのログインは不可能になります。
ユーザプロファイル のカスタマイズ¶
ユーザプロファイル変更 画面を OpenID 用にカスタマイズすることが可能です。 これは OpenID 情報を表示・修正する追加機能です。 アクセス制御の基本的な設定 で触れた、user関数を次のようにカスタマイズします。
修正前の user関数
def user(): return dict(form=auth())カスタマイズした user関数
def user(): if (request.args and request.args(0) == "profile"): form = DIV(auth(), auth.settings.login_form.list_user_openids()) return dict(form=form) return dict(form=auth())カスタマイズ後のプロファイル画面は次のようになります。
![]()
この画面から OpenIDの紐付けを管理することが可能になります。
もし拡張ログインフォームを使用している場合、上のカスタマイズコードではエラーになります。拡張ログインフォームでは alt_login_form 属性に指定することが必要だからです。このため拡張ログインフォームにも対応した、 修正したカスタマイズコードを次に示します。
def user(): if (request.args and request.args(0) == "profile"): if isinstance(auth.settings.login_form, ExtendedLoginForm): ext_form = auth.settings.login_form.alt_login_form else: ext_form = auth.settings.login_form form = DIV(auth(), ext_form.list_user_openids()) return dict(form=form)
OpenID 機能のカスタマイズ¶
OpenID 機能のカスタマイズについて考えてみます。現状の機能で不満な点は、次の2点ではないでしょうか。
- 最初のサインインの後に紐付けのため、Form認証でログインする必要がある。 このため操作が難しく、特にエンドユーザ向きではない。
- 簡単な操作で認証できる OpenID Authentication 2.0 の OP Identifier を使用したい。
これらの不満点について、カスタマイズでの対応を行なってみます。
紐付け処理の回避
1番目の問題は紐付けが無い OpenID に対して、ユーザを自動生成するという方法が考えられます。 次のような関数を作成します。
def _set_auth_user(self, db, oid, type_='openid'): ''' Return to a alt_login id. Register to the user/group/membership tables. ''' query = ((self.table_alt_logins.username == oid) & \ (self.table_alt_logins.type == type_)) alt_login = db(query).select().first() if not alt_login: import uuid # add to the user table if self.auth.settings.login_userfield: userfield = self.auth.settings.login_userfield elif 'username' in self.auth.settings.table_user: userfield = 'username' else: userfield = 'email' uname = 'anonymity_%s' % uuid.uuid4() uname = uname.replace('-','') user_row = self.auth.get_or_create_user({userfield:uname}) # add to the alt_logins table alt_id = self.table_alt_logins.insert(username=oid, type=type_, user=user_row.id) alt_login = self.table_alt_logins(alt_id) return alt_loginこの関数は登録した OpenID が存在しない場合、 auth_user と alt_logins テーブルにユーザを登録します。 また auth_userテーブルに登録するユーザのIDは、anoymity と UUID を組み合わせてユニークにするようにします。 関数は、default.py もしくは モジュールに追加します。
次に user関数( アクセス制御の基本的な設定 )を次のようにカスタマイズします。
def user(): if (request.args and request.args(0) == "login"): OpenIDAuth._find_matched_openid = set_auth_user return dict(form=auth())これは
OpenIDAuth
クラスにある _find_matched_openid メソッドを上書きしています。 これによって OpenID によるサインイン時に、 _set_auth_user 関数が動くようになります。OP Identifier を使用した認証
OpenIDを入力するのではなく、設定したプロバイダのアイコンをクリックすするだけで認証画面に遷移する方式です。
Python-OpenID は OP Identifier に対応していますので、次のようにカスタマイズすれば使用可能です。
def _provider_form(self, prv_formname, prv_url, prv_image): ''' Render the provider form. ''' def warning_openid_fail(session): session.warning = messages.openid_fail_discover form = FORM(INPUT(_type='image', _name='oid', _value=prv_url, _src=prv_image), _action=self.login_url) if form.accepts(request.vars, session, formname=prv_formname): oid = request.vars.oid consumerhelper = self._init_consumerhelper() url = self.login_url return_to_url = self.return_to_url try: if request.vars.has_key('_next'): return_to_url = self.return_to_url + '?_next=' + request.vars._next url = consumerhelper.begin(oid, self.realm, return_to_url) except DiscoveryFailure: warning_openid_fail(session) redirect(url) return form def _openid_form(self, style=None): ''' Assemble the openid login form ''' yahooj = self._provider_form('yahooj', 'yahoo.co.jp', 'http://i.yimg.jp/images/login/btn/btnXSLogin.gif') mixi = self._provider_form('mixi', 'mixi.jp', URL('static','images/mixi.gif')) google= self._provider_form('google', 'https://www.google.com/accounts/o8/id', 'http://www.google.com/images/logos/google_logo.gif') openid = self._login_form(style) form = TABLE(TR(TD(yahooj), (TD(mixi)), TD(google))) form = DIV(TR(form), TR(openid)) return form関数が2つありますが、_openid_form の方はプロバイダ毎のサインインフォームを生成します。 _provider_from は フォーム生成とバリデータ処理を定義しています。 関数は、default.py もしくは モジュールに追加します。
次に user関数( アクセス制御の基本的な設定 )を次のようにカスタマイズします。
def user(): if (request.args and request.args(0) == "login"): OpenIDAuth._form = _openid_form OpenIDAuth._provider_form = _provider_form return dict(form=auth())
OpenIDAuth
クラスにある _form メソッドを _provider_form に置き換えると共に、 クラスに _provider_form 関数を追加しています。最後にカスタマイズした認証画面を示します。
![]()
これらのカスタマイズは、クラスの内部メソッドを上書きしています。このため将来、バージョンアップ等で不具合が出る 可能性があります。