PythonのDjangoでOpenIDのOPを実装してSSO実現
OpenIDによるSSOを試したのでまとめました
OpenIDの仕様等に関する説明は一切しないので注意!
使ったもの
- python2.7
- django1.8 (多分django1.6以上なら大丈夫だと思います)
- python-openid (pipなりeasy_installでインストール)
- django_openid_provider 0.6 (Document)
- django-openid-auth 0.5
ざっくり説明すると
django_openid_providerはOP用のプラグインで
OpenIDRequestを受けてログイン状態を確認して、OpenIDResponseを返します
djangoの認証バックエンドとしてOpenIDが使えるようになります
今回の構成
それぞれのURLがやっていることはこんな感じ。適宜自分のURLに置き換えてください
あと、OPとRPは別プロジェクトじゃないと動きません。(たぶん)
OP側(django_openid_provider)
/provider/ OpenIDRequestの受け取りとOpenIDResponseの返し
/provider/xrds/ XRDS文書を返します
/provider/decide/ 確認ページを表示
/accounts/login/ 自分で実装する、OPでのログインをする部分。ログイン出来れば何でもいいです
RP側(django-openid-auth)
/consumer/login/ IdentityURLを使ってDiscovery,OpenIDRequestの送信
/consumer/complate/ OpenIDResponseの受け取りとRPでのログイン
/app/ OpenID認証させたいプログラム(なんでもいいです)
準備
両方共リンクからダウンロードしてきて各プロジェクトフォルダに入れた後、
必要な設定をしていきます(INSTALLED_APPSへの追加とルートのurls.pyへの追加は省略)
OP側
settings.py
LOGIN_URL='/accounts/login/'
LOGIN_REDIRECT_URL = '/provider/'
base.htmlをtemplates/openid_providerの中に追加します
blocktagが'content'のものさえ入っていれば良さそう
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>OpenIDSSO</title></head>
<body>{% block content %}
{% endblock %}
</body>
</html>
django1.8で動くようにプラグインのコードを少し修正
views.py(127行目)
#context_instance=RequestContext(request), mimetype=YADIS_CONTENT_TYPE)
context_instance=RequestContext(request), content_type=YADIS_CONTENT_TYPE)
RP側
ファイルを解凍するとREADMEにいろいろ書いてあるのでそこを見て設定します
settings.py
AUTHENTICATION_BACKENDS = (
'django_openid_auth.auth.OpenIDBackend',
'django.contrib.auth.backends.ModelBackend',
)OPENID_CREATE_USERS=True
LOGIN_URL='/consumer/login/'
LOGIN_REDIRECT_URL='/app/'
OPENID_SSO_SERVERL_URL = 'http://localhost:8000/provider/xrds/'
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' #READMEには書いてないですが、ないとエラーが出ます
同様にコードを修正
view.py(302行目)
#OPENID_LOGO_BASE_64.decode('base64'), mimetype='image/gif'
OPENID_LOGO_BASE_64.decode('base64'), content_type='image/gif'
urls.py(30行目)
#from django.conf.urls.defaults import *
from django.conf.urls import patterns, include, url
templates/openid/login.html(8行目)
background: url({% url openid-logo %}) no-repeat;←この行を削除
以上
mod_auth_openidのAX属性の罠
apacheのOpenID用モジュールmod_auth_openidで詰まった部分があったので紹介
インストールはここ参照です。
モジュールの設定
apacheを使って提供するWEBサービス上でOPからユーザー名を得るためにAX属性を使う事になります。
mod_auth_openidのAX属性の設定を追加します
AuthOpenIDAXReuire <alias> <type_url> <regex>
AuthOpenIDAXUsername <alias>
aliasは欲しい属性名、type_urlはOPが提供している属性に対応したURI、regexは取得した属性値の正規表現が入ります。AXRequireがOPへの属性値要求を行い、AXUsernameで指定した属性値がREMOTE_USERとしてapacheに渡されます
例
AuthOpenIDAXRequire email https://axschema.org/person/email @my\.email\.jp$
AuthOpenIDAXUsername email
検証
pythonのライブラリを使って試していたんですが、どうにもmod_auth_openidが属性パラメータを受け取ってくれない
原因はmod_auth_openid側がopenid.ax.count.<alias>が来ているかどうかにかかわらず、常にAX属性値を一つしか想定していないせいでした。(ちなみにpythonのライブラリは個数に拘らず複数の形で渡します)
つまり
OPが渡すパラメータ:openid.ax.value.email.1
mod_auth_openidが期待するパラメータ:openid.ax.value.email
となっており、mod_auth_openidがパラメータを見つけられずエラーを出してました。
とりあえずAX属性値を単数の形でOP側から渡してみたらちゃんと動いたのでよしとします。
python 変数のraw文字列化
これでいけます
'%r'%hogehoge
hogehogeがraw文字列化したい変数です
試しに使ってみる
>>> hogehoge = "\n"
>>> print (hogehoge)
>>> print ('%r'%hogehoge)
'\n'
【孔明の罠】ConfigParserのValueError
pythonでiniファイルの操作をするときに使うConfigParserモジュール
何故かvalueの中に'%'が入っているとエラーを出す
>>> from configparser import SafeConfigParser
>>> ini = SafeConfigParser()
>>> ini.read("file.ini")
[]
>>> ini.add_section("section")
>>> ini.set("section","option","%")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.4/configparser.py", line 1167, in set
super().set(section, option, value)
File "/usr/lib/python3.4/configparser.py", line 872, in set
value)
File "/usr/lib/python3.4/configparser.py", line 382, in before_set
"position %d" % (value, tmp_value.find('%')))
ValueError: invalid interpolation syntax in '%' at position 0
ここで中身を見てみると、'%'を'%%'に置換してから渡せばいいらしい
というわけで試してみると無事回避出来ました。めでたしめでたし
>>> a = '%'
>>> ini.set("section","option",a.replace('%','%%'))
>>> ini.get("section","option")
'%'
apacheのモジュールを使って簡単OpenID対応
今更感ありますが、apacheにモジュールをインストールして手間をかけずにOpenIDに対応する方法の紹介です
ここではローカルにOpenIDのOP(認証サーバのこと)も立てて動作の確認までしていきます
モジュールのインストールに関しては、ほとんどここを参考にしました
使ったもの
OS:CentoOS6.7
WEBサーバー:apache2.2
使うモジュール:mod_auth_openid
まずはリポジトリの登録
rpm --import http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6
rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
rpm --import http://rpms.famillecollet.com/RPM-GPG-KEY-remi
rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
そしてモジュールインストール、yumで一発(ソースコードからコンパイルしてたときの苦労はなんだったのか)
/etc/httpd/conf.dに mod_auth_openid.confができるのでそこに設定を入れていきます
LoadModule authopenid_module modules/mod_auth_openid.so
<Location "/">
AuthType OpenID
require valid-user
AuthOpenIDDBLocation /var/lib/mod_auth_openid/database.db
AuthOpenIDTrusted ^http://localhost:8000
AuthOpenIDSingleIdP http://localhost:8000/yadis
</Location>
一応説明しておくと、それぞれの意味はこんな感じです
AuthOpenIDDBLocation=セッション管理にsqliteを使用しているので、そのdbファイルの位置です
AuthOpenIDTrusted=認証先として信用するOPのURLです
AuthOpenIDSingleIdp=使用するOPのIdentifyURL(XRDS文書(XML)を渡してくるURLもしくはXRI) 設定しない場合は認証前にIdentifyURLを入力する画面が表示されます
設定が終わったので起動し、自動的に起動するように設定変更
service httpd start
chkconfig httpd on
次にpython-openidにおいてあるOPのサンプルで認証動作を確認したいと思います
まずpipで必要なライブラリをインストールした後、ソースコードを取って来て実行
git clone https://github.com/openid/python-openid.git
python server.py
試しにブラウザでapacheのURLにアクセスして見ると今さっき立てたOPにリダイレクトされます
おまけ
mod_auth_openidcの場合
yum groupinstall 'Development tools' -y
rpm -ivh http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
yum install httpd mod_ssl httpd-devel curl hiredis jansson -y
rpm -ivh https://github.com/pingidentity/mod_auth_openidc/releases/download/v1.8.5/mod_auth_openidc-1.8.5-1.el6.x86_64.rpm
yum install mod_auth_openidc -y
でインストールできます
pythonでsvnをcommitしよう
pythonでローカルにインストールしたSubversionを操作してcommitとかcopyをする方法まとめ
使うもの
- python 2.7以上
- subversion 1.6以上
- svn 0.3.36 ←これを使って操作していきます
準備
pipでsvnをインストール、以上
$pip install svn
使い方
基本的に、操作したいリポジトリがローカルにある場合はsvn.local.Localclient、httpなりhttpsなりを使ってアクセスしたい場合はsvn.remote.Remoteclientを使うみたいです
しかし問題はupdate,commitのサブコマンド用メソッドが用意されていない!
というわけで代わりにrun_commandというメソッドを使っていきます
def run_command(self, subcommand, args, success_code=0,
return_stderr=False, combine=False, return_binary=False):
引数としてsubversionのサブコマンドと、オプションをリストで渡して使う感じです
例
checkoutだとメソッドがあるから
r = svn.local.LocalClient("hogehoge/fugafuga",username="hogehoge",password="fugafuga")
r.checkout('') #カレントディレクトリにcheckout
commitだと
r=svn.remote.RemoteClient("https://hogehoge.fugafuga")
r.run_command("commit",["-m hogehoge"])
updateだと
r.run_command("update",[])
copyだと
r.run_command("copy",["https://hogehoge.com/trunk/fugafuga1","https://hogehoge.com/trunk/fugafuga2","-m hogehoge"])
こんな感じです