RPXを使って認証を賢く簡単に
- OpenIDだとかFacebook認証だとか、おじさんにはどういう仕組になっているのだか全然わからないのだが、世の中どんどん進化している。
- この手の認証サービスを活用することで、利用者もサービス提供側も幸せになれる。
- 利用者は新たなID/Passwordの組合せを覚える必要がなくなる。
- サービス提供側は実装も運用も軽くなる。ID/Passwordに絡む処理の多くを削れるし、漏洩防止のための運用も楽になる。
- とはいうものの、OpenID、Facebook Connect、Twitter API等々複数存在する認証全てに対応しようとすると、それはそれで結構しんどい。
- そこでRPXの登場となる。RPXはOpenIDやFacebook Connectなどの認証を統合するAPIを提供してくれる。
RPXを使った認証の流れ
RPXを使う準備
- RPXを使うには、利用者登録をする必要がある。→登録
- Basic Accountだと無料。ただし、使える機能は最低限。
- 利用者登録したら、自分のアプリケーションも登録する必要あり。→アプリケーション登録
- その登録したアプリケーションに対する、RPXの認証フォームウィジェットのコードを取得する。→ウィジェットコード取得ページ
- 例えばhttp://localhost/token_url/ にてRPXからの戻りを取り込む場合、以下のようなHTMLコードが生成されているはずである。
<iframe src="http://another-test.rpxnow.com/openid/embed?token_url=http%3A%2F%2Flocalhost%2Ftoken_url" scrolling="no" frameBorder="no" allowtransparency="true" style="width:400px;height:240px"></iframe>
- このコードを、認証フォームを表示したいページのテンプレートに貼り込めば良いのである。
Pylonsを使った実装例
- Pylons+SimpleDB+OpenIDチュートリアル風味同様の簡単なアプリケーションを開発。
- ブラウザで当該アプリケーションにアクセスすることで、そのサーバの時刻を知ることができる。
- RPX使ってログインすることで、サーバとの時差を設定し、かつ、時差を計算した時刻を表示できる。
- RPXを使ってログインした人は、ニックネームを設定できる。
- 時差やニックネームは、サーバ側に保存でき、次回ログインした時に取り出せる。
[app:main] use = egg:RPXClock full_stack = true static_files = true cache_dir = %(here)s/data beaker.session.key = rpxclock beaker.session.secret = somesecret rpx_token = RPXサイトが発行するAPI Key(下図)を入力 sdb_user_openid_domain = SimpleDB上にデータを保存するドメイン名
- 表示を担当するcontrollers/clock.py
import logging from pylons import request, response, session, tmpl_context as c from pylons.controllers.util import abort, redirect_to from rpxclock.lib.base import BaseController, render import datetime log = logging.getLogger(__name__) from rpxclock.lib import helpers as h from rpxclock.lib.auth import * from rpxclock.lib.sdb import UserPropertySDB import formencode from pylons.decorators.rest import restrict from pylons.decorators import validate from rpxclock.model import UserProperty class TimeDiffForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True nickname = formencode.validators.String(not_empty = True) time_diff = formencode.validators.Int(not_empty = True) class ClockController(BaseController): def __init__(self): BaseController.__init__(self) self.ups = UserPropertySDB() def now(self): if get_identifier(): return self._registered() else: return self._guest_now() def _registered(self): up = self.ups.find(session['identifier']) c.title = 'My Clock' if up: name = up.nickname time_diff = up.time_diff else: name = session['identifier'] time_diff = 0 c.heading = 'Welcome! %s' % name c.content = "Current time is %s" % str(datetime.datetime.now() + datetime.timedelta(0, 0, 0, 0, 0, time_diff)) return render('/derived/clock/registered.html') def _guest_now(self): c.title = 'My Clock' c.heading = 'Welcome!' c.content = "Current time is %s" % str(datetime.datetime.now()) return render('/derived/clock/guest.html') @require_login def customize(self): return render('/derived/clock/customize.html') @restrict('POST') @validate(schema = TimeDiffForm(), form = 'time_diff') @require_login def save(self): up = UserProperty(session['identifier'], self.form_result['nickname'], int(self.form_result['time_diff'])) self.ups.save(up) redirect_to('now')
-
- lib/auth.pyで定義されているget_identifier()を見ることで、RPXにログインしているかどうかがわかる。 (in now())
- デコレータ@require_loginも、lib/auth.pyで定義されている。
- lib/auth.py
from pylons.controllers.util import abort, redirect_to, url_for from pylons import session from decorator import decorator def get_identifier(): if 'identifier' in session: return session['identifier'] else: return None def require_login(func, *args, **kwargs): if get_identifier() is None: redirect_to(url_for(controller = 'clock', action = 'now')) return func(*args, **kwargs) require_login = decorator(require_login)
-
- get_identifier()はセッション変数中のidentifierエントリをみているだけ。
- セッションにidentifierを入れるのは、controller/auth.py
- controllers/auth.py
import logging from pylons import config, request, response, session, tmpl_context as c from pylons.controllers.util import abort, redirect_to, url_for from rpxclock.lib.base import BaseController, render log = logging.getLogger(__name__) import urllib2 import md5 import simplejson as json_ class AuthController(BaseController): def on_login(self): pass def on_logout(self): pass def logout(self): session['identifier'] = None del session['identifier'] session.save() redirect_to(url_for(controller = "clock", action = "now")) def rpx_token_url(self, *args, **kargs): 'token' in request.params or redirect_to(url_for(controller="clock", action="now")) token = request.params['token'] # contact rpx for the details: url = "https://rpxnow.com/api/v2/auth_info?token=%s&apiKey=%s" % (token, config.get('rpx_token')) json = json_.loads(urllib2.urlopen(url).read()) if(json['stat'] == "ok"): json = json["profile"] session['identifier'] = json['identifier'] session.save() redirect_to(url_for(controller="clock", action="now"))
-
- rpx_token_urlは、RPXでの処理が終わった後呼び出されるエントリ。
- POSTに入ってくるtokenを取り出し、それを元にRPXからユーザプロファイル情報を引っ張る。
- 得られた結果をjsonでパースし、profileの中からidentifierを取り出す。これが認証後のキーとなる。
- sessionに入れて、時計表示のnowにリダイレクト。