web2py version: 1.99.4

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
設定前に アクセス制御の基本的な設定 を完了しておく必要があります。また 各認証方式での挙動の違い も参照下さい。
モデル定義(db.pyなど)にて、 login_formOpenIDAuth インスタンスを設定します。

設定例

from gluon.contrib.login_methods.openid_auth import OpenIDAuth
auth.settings.login_form = OpenIDAuth(auth)

OpenIDAuth コンストラクタのパラメータには、 Auth インスタンスを指定する必要があります。

OpenIDモジュールは、Google AppEngine(GAE)でも動作可能です。設定は次のページを参照してください。

認証用クラス

このクラスのコンストラクタのパラメータについて説明します。

class openid_auth.OpenIDAuth(auth)
auth
Auth インスタンスを指定します。

使用例

OpenID は他の認証方式と比べて複雑な動きをします。このためまず、基本的な設定でのシステム動作の説明をします。

基本的なシステム動作

from gluon.contrib.login_methods.openid_auth import OpenIDAuth
auth.settings.login_form = OpenIDAuth(auth)

この設定の場合のシステム動作は次のようになります。

[1] OpenID のサインイン画面

../../../_images/web2py_ac_012r.JPG
../../../_images/web2py_ac_080r.PNG

[2] プロバイダの認証画面

../../../_images/web2py_ac_013r.JPG ../../../_images/web2py_ac_014r.JPG

Yahoo Japan の場合

../../../_images/web2py_ac_080r.PNG

[3] ユーザ紐付け

../../../_images/web2py_ac_015r.JPG

ログイン

../../../_images/web2py_ac_080r.PNG

[4]

完了

../../../_images/web2py_ac_085r.PNG ../../../_images/web2py_ac_084r.PNG

[6] ログアウト

../../../_images/web2py_ac_082r.PNG

[5] 紐付け用ユーザ登録

../../../_images/web2py_ac_020r.JPG

ユーザ登録

[1]OpenIDを入力し、サインインします。
[2]
  • 入力した OpenIDのプロバイダ認証画面に遷移しますので、ログインします。
  • ログインした OpenID は alt_logins テーブルに自動保存されます。
[3]
  • auth_user テーブルのユーザIDと OpenID の紐付けを行う必要があります。
  • Form認証で紐付けしたいユーザIDを使ってログインすることにより、紐付けが完了します。
  • 既に紐付いているユーザIDを使用した場合は、紐付けが失敗します。
  • 紐付けしたいユーザが存在しない場合、ユーザ登録で新しいユーザを登録します。
[4]紐付けが完了し、OpenID を使用しシステムにアクセスできるようになりました。
[5]新規のユーザを登録します。
[6]ユーザ登録するとシステムにログインしますが、まだ OpenID との紐付けが終わっていません。 ログアウトしてから OpenIDを使ったサインインを行い、紐付けの処理をします。

OpenIDと通常のユーザIDとの、紐付け処理を行う必要があります。このため他の認証方式と比べても、複雑にシステムが動作します。 なお、OpenIDのサインイン画面イメージは次のようになります。

../../../_images/web2py_ac_012r.JPG

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())

カスタマイズ後のプロファイル画面は次のようになります。

../../../_images/web2py_ac_021r.JPG

この画面から 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点ではないでしょうか。

  1. 最初のサインインの後に紐付けのため、Form認証でログインする必要がある。 このため操作が難しく、特にエンドユーザ向きではない。
  2. 簡単な操作で認証できる OpenID Authentication 2.0 の OP Identifier を使用したい。

これらの不満点について、カスタマイズでの対応を行なってみます。

  1. 紐付け処理の回避

    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 関数が動くようになります。

  2. 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 関数を追加しています。

    最後にカスタマイズした認証画面を示します。

    ../../../_images/web2py_ac_022r.JPG

これらのカスタマイズは、クラスの内部メソッドを上書きしています。このため将来、バージョンアップ等で不具合が出る 可能性があります。