2015-01-13 16 views
11

私はPythonでSAML 2.0ベースのサービスプロバイダを実装しようとしています。PythonのSAML 2.0サービスプロバイダ

私のウェブアプリケーションは現在すべてFlaskアプリケーションです。私はFlaskの青写真/デコレータを作って、既存のアプリケーションにシングルサインオン機能を追加する計画です。

私はpython-samlをよく見ましたが、不幸にも、あまりにも多くの既存のサーバー/アプリケーションが存在し、互換性がないため、解決する価値のない依存性の問題があります。

PySAML2が動作するように見えますが、ほとんどのドキュメントはありません。どのドキュメントが利用可能かはわかりません。 Flaskアプリケーションで使用されるPySAML2の例はありません。

私が持っているアイデンティティプロバイダはOktaです。私はOktaでログインした後、自分のアプリにリダイレクトされるようにOktaを設定しました。

PySAML2の使用に関するアドバイスや、自分のアプリケーションにアクセスしているSAML 2.0を使用しているユーザーを最もよく認証する方法に関するアドバイスはありますか?

答えて

14

更新:using PySAML2 with Oktaの詳細については、developer.okta.comをご覧ください。

以下は、Python/FlaskでSAML SPを実装するためのサンプルコードです。このサンプルコードでは、いくつかのことを示します:

  1. 複数のIdPをサポートしています。
  2. ユーザー管理のためにFlask-Loginを使用する。
  3. 視聴制限として「SSO URL」を使用する(IdPの設定を簡略化するため)。
  4. ユーザーのプロビジョニング(SAML JIT)
  5. 属性ステートメントで追加のユーザー情報を渡す。

は何SP開始し、認証要求を行っている実証されていない- 私は後でそれをフォローます。

ある時点で、私はpysaml2の周りにラップトップを作成して、デフォルトを説得したいと考えています。

最後に、python-samlのように、pysaml2ライブラリはxmlsec1バイナリを使用します。これはまた、サーバー環境に依存関係の問題を引き起こす可能性があります。その場合はxmlsec1signxmlライブラリに置き換えてください。以下のサンプルで

すべては、以下の設定で動作するはずです:

$ virtualenv venv 
$ source venv/bin/activate 
$ pip install flask flask-login pysaml2 

最後に、あなたが仕事に、このためにOkta側に物事を行う必要があります。

最初に、Oktaアプリケーション構成の一般タブで、「FirstName」および「LastName」属性ステートメントを送信するようにアプリケーションを構成します。Adding Attribute Statements to an Okta application

第二:あなたのOktaアプリケーション構成のタブでシングルサインオンでは、URLに取り、example.okta.com.metadataという名前のファイルに入れて。あなたは以下のようなコマンドでこれを行うことができます。ここで

$ curl [the metadata url for your Okta application] > example.okta.com.metadata 

Where to find the metadata url for an Okta application

のIdPを処理するために、あなたのPython /フラスコアプリケーションがSAML要求を開始するためにあなたが必要とするものである。このため

# -*- coding: utf-8 -*- 
import base64 
import logging 
import os 
import urllib 
import uuid 
import zlib 

from flask import Flask 
from flask import redirect 
from flask import request 
from flask import url_for 
from flask.ext.login import LoginManager 
from flask.ext.login import UserMixin 
from flask.ext.login import current_user 
from flask.ext.login import login_required 
from flask.ext.login import login_user 
from saml2 import BINDING_HTTP_POST 
from saml2 import BINDING_HTTP_REDIRECT 
from saml2 import entity 
from saml2.client import Saml2Client 
from saml2.config import Config as Saml2Config 

# PER APPLICATION configuration settings. 
# Each SAML service that you support will have different values here. 
idp_settings = { 
    u'example.okta.com': { 
     u"metadata": { 
      "local": [u'./example.okta.com.metadata'] 
     } 
    }, 
} 
app = Flask(__name__) 
app.secret_key = str(uuid.uuid4()) # Replace with your secret key 
login_manager = LoginManager() 
login_manager.setup_app(app) 
logging.basicConfig(level=logging.DEBUG) 
# Replace this with your own user store 
user_store = {} 


class User(UserMixin): 
    def __init__(self, user_id): 
     user = {} 
     self.id = None 
     self.first_name = None 
     self.last_name = None 
     try: 
      user = user_store[user_id] 
      self.id = unicode(user_id) 
      self.first_name = user['first_name'] 
      self.last_name = user['last_name'] 
     except: 
      pass 


@login_manager.user_loader 
def load_user(user_id): 
    return User(user_id) 


@app.route("/") 
def main_page(): 
    return "Hello" 


@app.route("/saml/sso/<idp_name>", methods=['POST']) 
def idp_initiated(idp_name): 
    settings = idp_settings[idp_name] 
    settings['service'] = { 
     'sp': { 
      'endpoints': { 
       'assertion_consumer_service': [ 
        (request.url, BINDING_HTTP_REDIRECT), 
        (request.url, BINDING_HTTP_POST) 
       ], 
      }, 
      # Don't verify that the incoming requests originate from us via 
      # the built-in cache for authn request ids in pysaml2 
      'allow_unsolicited': True, 
      'authn_requests_signed': False, 
      'logout_requests_signed': True, 
      'want_assertions_signed': True, 
      'want_response_signed': False, 
     }, 
    } 

    spConfig = Saml2Config() 
    spConfig.load(settings) 
    spConfig.allow_unknown_attributes = True 

    cli = Saml2Client(config=spConfig) 
    try: 
     authn_response = cli.parse_authn_request_response(
      request.form['SAMLResponse'], 
      entity.BINDING_HTTP_POST) 
     authn_response.get_identity() 
     user_info = authn_response.get_subject() 
     username = user_info.text 
     valid = True 
    except Exception as e: 
     logging.error(e) 
     valid = False 
     return str(e), 401 

    # "JIT provisioning" 
    if username not in user_store: 
     user_store[username] = { 
      'first_name': authn_response.ava['FirstName'][0], 
      'last_name': authn_response.ava['LastName'][0], 
      } 
    user = User(username) 
    login_user(user) 
    # TODO: If it exists, redirect to request.form['RelayState'] 
    return redirect(url_for('user')) 


@app.route("/user") 
@login_required 
def user(): 
    msg = u"Hello {user.first_name} {user.last_name}".format(user=current_user) 
    return msg 


if __name__ == "__main__": 
    port = int(os.environ.get('PORT', 5000)) 
    if port == 5000: 
     app.debug = True 
    app.run(host='0.0.0.0', port=port) 
+0

おかげで多くのことを。これは非常に役に立ちます。あなたがSPで始動した例があればそれは素晴らしいでしょう! また、ここで最新バージョンのpysaml2を使用していますか? – steve

+0

よろしくお願いします!はい、私は数日後にSP開始例を追加する予定です。 –

+0

pysaml2のバージョンが最新であるかどうかわかりません。上記の例で使用した 'pip freeze'のバージョンは' pysaml2 == 2.2.0'です。 –

関連する問題