SAML 2.0服务提供者在Python中 [英] SAML 2.0 Service Provider in 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
什么是不是显示是由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:
- Supporting multiple IdPs.
- Using Flask-Login for user management.
- Using the "SSO URL" as the audience restriction (to simplify configuration on the IdP).
- Just in time provisioning of users ("SAML JIT")
- 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屋!