SAML 2.0服务提供者在Python中 [英] SAML 2.0 Service Provider in Python

查看:272
本文介绍了SAML 2.0服务提供者在Python中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我的web应用程序目前都是Flask应用程序。我打算制作一个Flask blueprint / decorator,它允许我将单点登录功能放到已经存在的应用程序中。



我已经看过 python-saml ,不幸的是,依赖问题不值得解决,因为我有太多的先前存在的服务器/不兼容。



PySAML2 的搭配喜欢它可以工作,但是没有什么文档,什么文档可用我有麻烦理解。在Flask应用程序中没有使用PySAML2的例子。



身份提供者是Okta。我有Okta设置,所以,我在Okta登录后,我被重定向到我的应用程序。

任何人都可以提供任何关于使用PySAML2的建议,或者也许建议如何使用SAML 2.0谁正在访问我的应用程序最好的身份验证用户?

解决方案

更新:使用PySAML2与Okta 现在位于developer.okta.com。



以下是在Python / Flask中实现SAML SP的一些示例代码。这个示例代码演示了几件事情:
$ b $ ol
支持多个IdP

  • 使用 Flask-Login 用于用户管理。

  • 使用SSO URL作为用户的限制(简化IdP配置)。
  • 准时提供用户(SAML JIT)

  • 传递额外的用户信息属性语句

  • 什么是不是显示是由SP发起的身份验证请求 - 我会跟进后来。

    在某些时候,我希望创建一个包含pysaml2的包装,这个包装有默认的默认值。



    最后,像python-saml一样,pysaml2库使用 xmlsec1 二进制文件。这也可能会导致服务器环境中的依赖性问题。如果是这样的话,你需要考虑用 xmlsec1 > signxml 库。



    以下示例中的所有内容都应该按照以下步骤操作:

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


    $
    $ b

    第一:在常规中,在您的Okta应用程序配置的选项卡中,配置应用程序以发送名字和姓氏属性语句。第二种:将属性语句添加到Okta应用程序中

    在您的Okta应用程序配置的单点登录选项卡中,取出url并将其放入名为 example.okta.com.metadata 。你可以用下面的命令来做到这一点。

      $ curl [Okta应用程序的元数据网址]> example.okta.com.metadata 



    以下是Python / Flask应用程序需要的来处理IdP发起的SAML请求:
    $ b $ $ $ p $ $
    导入记录
    导入os
    导入urllib
    导入uuid
    导入zlib $ b $烧瓶导入烧瓶
    烧瓶导入重定向$从烧瓶进口请求b $ b从烧瓶进口请求
    从flask.ext.login进口url_for
    从flask.ext.login进口LoginManager
    从flask.ext.login导入进口UserMixin
    current_user $ b $来自flask.ext.login从flask.ext.login导入login_required
    从saml2导入login_user
    从saml2导入BINDING_HTTP_POST
    从saml2导入BINDING_HTTP_REDIRECT
    导入实体$来自saml2.client的b $ b从saml2.config导入Saml2Client
    导入配置为Saml2Config

    #PER应用程序配置设置。
    #您所支持的每个SAML服务在这里都有不同的值。
    idp_settings = {
    u'example.okta.com':{
    umetadata:{
    local:[u'./ example.okta.com.metadata ']
    }
    },
    }
    app = Flask(__ name__)
    app.secret_key = str(uuid.uuid4())#用你的密钥
    login_manager = LoginManager()
    login_manager.setup_app(app)
    logging.basicConfig(level = logging.DEBUG)
    #用你自己的用户存储代替
    user_store = {}

    $ b $ 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']
    除外:
    传递


    @login_manager。 user_loader
    def load_user(user_id):
    返回用户(user_id)


    @ app.route(/)
    def main_page():
    返回Hello


    @ app.route(/ saml / sso /< idp_name>,methods = ['POST'])
    def idp_initiated(idp_name):
    settings = idp_settings [idp_name]
    settings ['service'] = {
    'sp':{
    'endpoints':{$ b $'assertion_consumer_service':[
    (request。 url,BINDING_HTTP_REDIRECT),
    (request.url,BINDING_HTTP_POST)
    ],
    },
    #不要验证传入的请求是否通过
    # autys请求id的内置缓存在pysaml2
    'allow_unsolicited':True,
    'authn_requests_signed':False,
    'logout_requests_signed':True,
    'want_assertions_signed':True ,
    'want_response_signed':False,



    spConfig = Saml2Config()
    spConfig.load(设置)
    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
    除了Exception:e
    logging.error(e)
    valid = False
    return str(e),401

    #JIT provisioning
    如果用户名不在user_store中:
    user_store [username] = {
    'first_name':authn_response.ava ['FirstName'] [0],
    'last_name':authn_response.ava ['LastName'] [0],
    }
    用户=用户(用户名)
    登录用户(用户)
    #TODO:如果存在,重定向到request.form ['RelayState']
    返回重定向(url_for('user'))


    @ app.route(/用户)
    @login_required
    def user():
    msg = uHello {user.first_name} {user.last_name}。format(user = current_user)
    return msg

    $ b如果__name__ ==__main__:
    port = int(os.environ.get('PORT',5000))
    if port == 5000:
    app.debug = True
    app.run(host ='0.0.0.0',port = port)


    I am looking to implement a SAML 2.0 based service provider in Python.

    My web apps are currently all Flask applications. I plan to make a Flask blueprint/decorator that allows me to drop single sign-on capabilities into preexisting applications.

    I have looked into python-saml extensively and unfortunately there are dependency issues that are not worth resolving, as I have too many preexisting servers/apps whos environments won't be compatible.

    PySAML2 looks like it could work, however there is little documentation, and what documentation is available I have trouble comprehending. There are no examples of PySAML2 used in a Flask app.

    The Identity Provider I have is Okta. I have Okta set up so that after I login at Okta, I am redirected to my app.

    Can anyone offer any advice on using PySAML2, or perhaps advice on how to best authenticate a user using SAML 2.0 who is visiting my application?

    解决方案

    Update: A detailed explanation on using PySAML2 with Okta is now on developer.okta.com.

    Below is some sample code for implementing a SAML SP in Python/Flask. This sample code demonstrates several things:

    1. Supporting multiple IdPs.
    2. Using Flask-Login for user management.
    3. Using the "SSO URL" as the audience restriction (to simplify configuration on the IdP).
    4. Just in time provisioning of users ("SAML JIT")
    5. Passing additional user information in Attribute Statements.

    What is not demonstrated is doing SP initiated authentication requests - I'll followup with that later.

    At some point, I hope to create a wrapper around pysaml2 that has opinionated defaults.

    Lastly, like python-saml, the pysaml2 library makes use of the xmlsec1 binary. This might also cause dependency issues in your server environments. If that's the case, you'll want to look into replacing xmlsec1 with the signxml library.

    Everything in the sample below should work with the following setup:

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

    Finally, you'll need to do to things on the Okta side for this to work.

    First: In the General tab of your Okta application configuration, configure the application to send the "FirstName" and "LastName" Attribute Statements.

    Second: In the Single Sign On tab of your Okta application configuration, take of the url and put them in a file named example.okta.com.metadata. You can do this with a command like the one below.

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

    Here is what you'll need for your Python/Flask application to handle IdP initiated SAML requests:

    # -*- 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)
    

    这篇关于SAML 2.0服务提供者在Python中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆