この記事を読むとできるようになること
- pythonを用いて実際にsamlのシングルサインオンを実装するための方法を理解できる
- samlによる通信のデバッグ方法を理解できる
実装方法
SP主導のシングルサインオンを前提とする
ライブラリのインストール
SP側で使用するライブラリとして、saml認証用のpython3-samlを使用する。なお、このライブラリはlibxml2-devやlibxmlsec1などに依存しており、macであればhomebrewなどでマシン上にインストールしておく必要があるので注意
pip install python3-saml
IdPでアプリケーション作成
IdPはoktaを利用する。アカウント作成後、管理画面にアクセスしテスト用のアプリケーションを作成しておく。(30日間の無料トライアルがある)。作成は、Create App Integrationボタンから可能
Sign-in methodはSAML 2.0を選択
Configure SAMLでSingle sign-on URLにはSP側の認証応答メッセージを受けるためのPOSTリクエストエンドポイントを、SP Entity IDにはSPを一意に特定するための名称を入力する
SPに登録するIdPの情報を取得する
サインオンタブを選択
IdPのSign on URL(認証応答メッセージをSPから送信する先のエンドポイント)とIssuer(IdPのエンティティID)、Signing Certificate(公開鍵暗号方式における公開鍵の証明書)を控える
SP側でエンドポイントを作成する
- IdPへの認証要求メッセージ送信(リダイレクト)用 - GET
- IdPからの認証応答メッセージ受信用 - POST
※このコードはFlaskでの基本的な例であり、実際の使用には適切なエラーハンドリングやセキュリティ対策が必要
IdPへの認証要求メッセージ送信(リダイレクト)用 - GET
sso_login()のメソッドに来たリクエストをもとに、SP側で認証応答メッセージを作成し、IdPにリダイレクトする。prepare_flask_request
関数は、Flaskのリクエストオブジェクトをpython3-samlが期待する形式に変換する
IdPからの認証応答メッセージ受信用 - POST
sso_acs()のメソッドにきたリクエストは、IdPからのリクエスト。認証応答メッセージを検証し、SP側でも認証済みとする(コードサンプルなので、SP側で認証済みとする処理は割愛)。
from flask import Flask, request, redirect
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.utils import OneLogin_Saml2_Utils
app = Flask(__name__)
@app.route('/sso/login', methods=['GET', 'POST'])
def sso_login():
req = prepare_flask_request(request)
auth = OneLogin_Saml2_Auth(req, saml_settings)
auth.login()
return redirect(auth.get_redirect_url())
@app.route('/sso/acs', methods=['POST'])
def sso_acs():
req = prepare_flask_request(request)
auth = OneLogin_Saml2_Auth(req, saml_settings)
auth.process_response()
errors = auth.get_errors()
if len(errors) == 0:
if not auth.is_authenticated():
return 'Not authenticated', 401
else:
return 'Authenticated'
else:
return 'Errors: ' + ', '.join(errors), 500
def prepare_flask_request(request):
url_data = urlparse(request.url)
return {
'https': 'on' if request.scheme == 'https' else 'off',
'http_host': request.host,
'server_port': url_data.port,
'script_name': request.path,
'get_data': request.args.copy(),
'post_data': request.form.copy()
}
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8000)
saml_settings
は以下のような辞書をOneLogin_Saml2_Settingsに指定したオブジェクトのインスタンスとなる
from onelogin.saml2.settings import OneLogin_Saml2_Settings
OneLogin_Saml2_Settings(
{
"strict": True,
"debug": False,
"sp": {
"entityId": "これは、SP側のエンティティIdなので、SPを一意に特定するためのドメインを含む文字列を指定する",
"assertionConsumerService": {
"url": "http://sp.com/saml/acs/", # IdPからの認証応答メッセージを受けるためのPOSTエンドポイント
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"x509cert": "",
"privateKey": "",
},
"idp": {
"entityId": "http://idp.com/exk・・・", # 先ほど控えたIssuer(IdPのエンティティID)
"singleSignOnService": {
"url": "https://ipd.com/app/・・・/sso/saml", # 先ほど控えたIdPのSign on URL(認証応答メッセージをSPから送信する先のエンドポイント)
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
},
"x509cert": "MIID・・・, # 先ほど控えたSigning Certificate(公開鍵暗号方式における公開鍵の証明書)
},
}
)
余談:python3-samlの注意点
python3-samlの依存ライブラリであるlxmlについて、本記事投稿時点では、lxml==5.1.0を使用すると、プログラムがエラーメッセージなしでこの行でクラッシュする(しかもたまに成功する)というバグをはらんでいる。
lxml==4.9.3に下げることでひとまずは解決することは可能。
デバッグ方法
開発者ツールでsaml認証をデバッグできるChrome拡張機能があるので紹介しておく
画面上部にはリクエスト内容(samlの場合はSAMLと表示される、赤枠箇所)が、画面下部にはsamlでやり取りされる実際のxmlの内容を確認することが可能
まとめ
いかがでしたでしょうか。本記事では、pythonを用いて実際にsamlのシングルサインオンを実装するための方法と、Google Chromeの拡張機能を用いたデバッグ方法について紹介しました。samlを用いたシングルサインオンのナレッジはまだまだネット上に少ないので、実際にSaasなどでシングルサインオンを実装する必要が生じた際は、ぜひ参考にしてみてください。