Cập nhật: Giải thích chi tiết về using PySAML2 with Okta hiện đã có trên developer.okta.com.
Dưới đây là một số mã mẫu để triển khai SAML SP trong Python/Flask. Mã mẫu này thể hiện một số điều:
- Hỗ trợ nhiều IdP.
- Sử dụng Flask-Login để quản lý người dùng.
- Sử dụng "URL SSO" làm hạn chế đối tượng (để đơn giản hóa cấu hình trên IdP).
- Chỉ trong thời gian cung cấp người dùng ("SAML JIT")
- Chuyển thông tin người dùng bổ sung trong Bảng sao kê thuộc tính.
không phải là đã chứng minh là đang thực hiện yêu cầu xác thực SP bắt đầu - Tôi sẽ theo dõi sau này.
Tại một thời điểm nào đó, tôi hy vọng sẽ tạo một trình bao bọc xung quanh pysaml2 có mặc định được đề xuất.
Cuối cùng, như python-saml, thư viện pysaml2 sử dụng mã nhị phân xmlsec1
. Điều này cũng có thể gây ra các vấn đề phụ thuộc trong môi trường máy chủ của bạn. Nếu đúng như vậy, bạn sẽ muốn xem xét thay thế xmlsec1
bằng thư viện signxml.
Tất cả mọi thứ trong ví dụ dưới đây nên làm việc với các thiết lập sau:
$ virtualenv venv
$ source venv/bin/activate
$ pip install flask flask-login pysaml2
Cuối cùng, bạn sẽ cần phải làm gì để mọi thứ ở phía Okta để làm việc này.
Đầu tiên: Trong tab Chung trong cấu hình ứng dụng Okta của bạn, định cấu hình ứng dụng để gửi báo cáo thuộc tính "FirstName" và "LastName".
Thứ hai: Trong Single Sign On tab cấu hình ứng dụng Okta của bạn, hãy url và đặt chúng trong một file có tên example.okta.com.metadata
. Bạn có thể làm điều này với một lệnh như dưới đây.
$ curl [the metadata url for your Okta application] > example.okta.com.metadata
Dưới đây là những gì bạn sẽ cần cho các ứng dụng Python/Flask bạn để xử lý IdP khởi xướng yêu cầu 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)
Cảm ơn rất nhiều cho việc này. Điều này rất hữu ích. Nếu bạn có một ví dụ khởi đầu SP sẽ tuyệt vời! Bạn cũng đang sử dụng phiên bản mới nhất của pysaml2 tại đây? – steve
Bạn được chào đón! Có, tôi dự định thêm một ví dụ khởi đầu SP trong một vài ngày. –
Tôi không chắc chắn phiên bản nào của pysaml2 là phiên bản mới nhất, theo 'pip freeze' phiên bản tôi đã sử dụng trong ví dụ trên là' pysaml2 == 2.2.0' –