PySAML2在Django中的集成
项目描述
djangosaml2
djangosaml2是一个Django应用程序,它将PySAML2库集成到您的项目中。这意味着您可以使用基于PySAML的服务提供商来保护您的Django项目。这样,它将与您的身份提供者进行SAML2通信,允许您使用此认证机制。本文档将指导您完成几个简单步骤以实现此目标。
安装
PySAML2使用xmlsec1二进制文件来签名SAML断言,因此您需要通过操作系统包或通过编译源代码来安装它。最终的可执行文件安装在哪里并不重要,因为您需要在配置阶段设置其完整路径。
现在,您可以使用easy_install或pip安装djangosaml2包。这将自动安装PySAML2及其依赖项。
配置
要使djangosaml2在您的Django项目中正常工作,您需要设置以下三项内容
settings.py 如您可能已经知道的,这是Django的主要配置文件。
urls.py 这是您将包含djangosaml2 URL的文件。
pySAML2 相关文件,例如属性映射目录和证书。
settings.py文件中的更改
首先,您需要将 djangosaml2 添加到已安装应用程序列表中。
INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.admin', 'djangosaml2', # new application )
实际上,这并不是必需的,因为 djangosaml2 不包含任何数据模型。我们之所以包括它,是为了能够从我们的项目中运行 djangosaml2 测试套件,您应该始终这样做,以确保它与您的 Django 版本和环境兼容。
然后,您必须将 djangosaml2.backends.Saml2Backend 身份验证后端添加到身份验证后端列表中。默认情况下,只有 Django 内置的 ModelBackend 已配置。一个典型的配置如下所示
AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', 'djangosaml2.backends.Saml2Backend', )
最后,我们必须告诉 Django 我们想要使用的新登录 URL
LOGIN_URL = '/saml2/login/' SESSION_EXPIRE_AT_BROWSER_CLOSE = True
在这里,我们告诉 Django,任何需要认证用户的视图都应该在用户之前未认证的情况下将用户浏览器重定向到该 URL。我们还告诉用户关闭浏览器时,会话应该终止。这在 SAML2 联邦中很有用,因为注销协议并不总是可用。
如果您想在项目中允许多个认证机制,应将 LOGIN_URL 选项设置为另一个视图,并在该视图中添加指向 /saml2/login/ 视图的链接。
urls.py文件中的更改
接下来,您需要将 djangosaml2.urls 模块包含到您的主 urls.py 模块中。
urlpatterns = patterns( '', # lots of url definitions here (r'^saml2/', include('djangosaml2.urls')), # more url definitions )
如您所见,我们正在将 djangosaml2.urls 包含在 saml2 前缀下。您可以使用自己的前缀,但请确保与 settings.py 文件中 LOGIN_URL 参数中放置的内容保持一致。
PySAML2特定文件和配置
一旦您完成 Django 项目的配置,您就必须开始配置 PySAML。如果您只使用该库,您必须将配置选项放在一个文件中,并使用该文件的路径初始化 PySAML2。
在 djangosaml2 中,您只需将相同的信息放入 Django settings.py 文件中的 SAML_CONFIG 选项下。
我们将看到保护 Django 项目的一个典型配置
from os import path import saml2 BASEDIR = path.dirname(path.abspath(__file__)) SAML_CONFIG = { # full path to the xmlsec1 binary programm 'xmlsec_binary': '/usr/bin/xmlsec1', # your entity id, usually your subdomain plus the url to the metadata view 'entityid': 'http://localhost:8000/saml2/metadata/', # directory with attribute mapping 'attribute_map_dir': path.join(BASEDIR, 'attribute-maps'), # this block states what services we provide 'service': { # we are just a lonely SP 'sp' : { 'name': 'Federated Django sample SP', 'endpoints': { # url and binding to the assetion consumer service view # do not change the binding or service name 'assertion_consumer_service': [ ('http://localhost:8000/saml2/acs/', saml2.BINDING_HTTP_POST), ], # url and binding to the single logout service view # do not change the binding or service name 'single_logout_service': [ ('http://localhost:8000/saml2/ls/', saml2.BINDING_HTTP_REDIRECT), ], }, # attributes that this project need to identify a user 'required_attributes': ['uid'], # attributes that may be useful to have but not required 'optional_attributes': ['eduPersonAffiliation'], # in this section the list of IdPs we talk to are defined 'idp': { # we do not need a WAYF service since there is # only an IdP defined here. This IdP should be # present in our metadata # the keys of this dictionary are entity ids 'https://localhost/simplesaml/saml2/idp/metadata.php': { 'single_sign_on_service': { saml2.BINDING_HTTP_REDIRECT: 'https://localhost/simplesaml/saml2/idp/SSOService.php', }, 'single_logout_service': { saml2.BINDING_HTTP_REDIRECT: 'https://localhost/simplesaml/saml2/idp/SingleLogoutService.php', }, }, }, }, }, # where the remote metadata is stored 'metadata': { 'local': [path.join(BASEDIR, 'remote_metadata.xml')], }, # set to 1 to output debugging information 'debug': 1, # certificate 'key_file': path.join(BASEDIR, 'mycert.key'), # private part 'cert_file': path.join(BASEDIR, 'mycert.pem'), # public part # own metadata settings 'contact_person': [ {'given_name': 'Lorenzo', 'sur_name': 'Gil', 'company': 'Yaco Sistemas', 'email_address': 'lgs@yaco.es', 'contact_type': 'technical'}, {'given_name': 'Angel', 'sur_name': 'Fernandez', 'company': 'Yaco Sistemas', 'email_address': 'angel@yaco.es', 'contact_type': 'administrative'}, ], # you can set multilanguage information here 'organization': { 'name': [('Yaco Sistemas', 'es'), ('Yaco Systems', 'en')], 'display_name': [('Yaco', 'es'), ('Yaco', 'en')], 'url': [('http://www.yaco.es', 'es'), ('http://www.yaco.com', 'en')], }, 'valid_for': 24, # how long is our metadata valid }
您必须根据此配置创建几个外部文件和目录。
在安装部分中提到了 xmlsec1 二进制文件。在这里,在配置部分中,您只需将 xmlsec1 的完整路径放入其中,以便 PySAML2 在需要时调用它。
attribute_map_dir 指向一个目录,其中包含用于将用户属性名称从多个标准翻译的属性映射。通常,只需复制默认的 PySAML2 属性映射即可,这些映射可以在源分发的 tests/attributemaps 目录中找到。
metadata 选项是一个字典,其中您可以定义远程实体多种类型的元数据。通常,最简单的一种是 local,您只需放置一个包含远程实体元数据内容的本地 XML 文件名。此 XML 文件应采用 SAML2 元数据格式。
“key_file”和“cert_file”选项引用了标准x509证书的两个部分。您需要它们来签署元数据以及加密和解密SAML2断言。
自定义和动态配置加载
默认情况下,djangosaml2从SAML_CONFIG设置读取pysaml2配置选项,但有时您希望从其他地方读取这些信息,例如文件或数据库。有时您甚至希望这个配置根据请求不同而不同。
从djangosaml2 0.5.0开始,您可以定义自己的配置加载器,这是一个接受请求参数并返回saml2.config.SPConfig对象的调用。为了做到这一点,您需要设置以下设置
SAML_CONFIG_LOADER = 'python.path.to.your.callable'
用户属性
在SAML 2.0身份验证过程中,身份提供者(IdP)将在成功身份验证后向服务提供者(SP)发送一个安全断言。这个断言包含关于已验证用户的信息。具体发送到每个SP的哪些属性取决于IdP的配置。
当在Django端接收到此类断言时,它将用于查找Django用户并为其创建会话。默认情况下,djangosaml2将对User模型中的‘username’属性进行查询,但您可以将它更改为User模型中的任何其他属性。例如,您可以使用‘email’属性进行此查找。为此,您应设置以下设置
SAML_DJANGO_USER_MAIN_ATTRIBUTE = 'email'
请使用唯一的属性设置此选项。否则,由于djangosaml2不知道应该选择哪个Django用户,身份验证过程将失败。
您可以配置djangosaml2在Django数据库中不存在用户时创建此类用户,或者您可能不希望允许不在您数据库中的用户。为此,您可以在settings.py文件中设置另一个选项
SAML_CREATE_UNKNOWN_USER = True
此设置默认为True。
您可能还想配置SAML2用户属性与Django用户属性的映射。默认情况下,仅映射User.username属性,但您可以添加更多属性或更改该属性。为此,您需要更改settings.py中的SAML_ATTRIBUTE_MAPPING选项
SAML_ATTRIBUTE_MAPPING = { 'uid': ('username', ), 'mail': ('email', ), 'cn': ('first_name', ), 'sn': ('last_name', ), }
其中此字典的键是SAML用户属性,值是Django User属性。
如果您正在使用Django用户配置文件对象来存储有关用户的额外属性,您可以向SAML_ATTRIBUTE_MAPPING字典添加这些属性。对于每个(键,值)对,djangosaml2将尝试在具有匹配字段的User模型中存储属性。如果没有匹配的字段,它将尝试与您的自定义配置文件模型相同。
有关Django配置文件模型的更多信息,请参阅
https://docs.django.ac.cn/en/dev/topics/auth/#storing-additional-information-about-users
有时您需要使用特殊的逻辑来更新用户对象,这取决于SAML2属性和上述映射,djangosaml2提供了一个您可以监听的Django信号。为此,您可以将以下代码添加到您的应用程序中
from djangosaml2.signals import pre_user_save def custom_update_user(sender=user, attributes=attributes, user_modified=user_modified) ... return True # I modified the user object
您的处理程序将接收用户对象、SAML属性列表和一个标志,告诉您用户是否已被修改并在处理程序执行后需要保存。如果您的处理程序修改了用户对象,则应返回True。否则应返回False。这样,djangosaml2将知道是否应该保存用户对象,因此您不需要这样做,并且不再发出保存方法的调用。
IdP设置
恭喜,您已成功配置了联合的SP端。现在您需要将此新SP的实体ID和元数据发送给IdP管理员,以便他们可以将它添加到信任服务列表中。
您可以通过启动Django开发服务器并访问http://localhost:8000/saml2/metadata URL来获取这些信息。如果您在Django SAML2 URL下使用了不同的URL前缀,您需要更正此URL。
SimpleSAMLphp问题
从SimpleSAMLphp 1.8.2版本开始,如果在SP配置中指定了属性,将出现问题。当SimpleSAMLphp元数据解析器将XML转换为自定义的PHP格式时,它会放置以下选项
'attributes.NameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
但是需要替换为以下选项
'AttributeNameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
否则,从IdP发送到SP的断言将具有错误的属性名称格式,pysaml2将会困惑。
此外,如果您在SimpleSAMLphp配置中有一个AttributeLimit过滤器,您需要在它之前启用另一个属性过滤器,以确保AttributeLimit不会从认证源删除属性。您需要添加的过滤器是一个类似于这样的AttributeMap过滤器
10 => array( 'class' => 'core:AttributeMap', 'name2oid' ),
测试
检查一切是否按预期工作的一个方法是启用以下URL
urlpatterns = patterns( '', # lots of url definitions here (r'^saml2/', include('djangosaml2.urls')), (r'^test/', 'djangosaml2.views.echo_attributes'), # more url definitions )
现在,如果您访问/test/ URL,您将看到您的SAML属性,还有一个链接进行全局注销。
您还可以使用以下命令运行单元测试
python tests/run_tests.py
如果您已安装tox,您可以直接在根目录中调用tox,它将在多个Python版本中运行测试。
常见问题解答
为什么不能将SAML实现为Django认证后端?
实际上,SAML认证并不像在登录表单上放置一组凭证那样简单。实际上,用户密码根本不会提供给服务提供者。这是设计使然。您必须将认证任务委托给IdP,然后从它那里获得异步响应。
尽管如此,djangosaml2确实使用Django认证后端将有关用户的SAML断言转换为Django用户对象。
为什么不在Django中间件类中放 everything,使我们的生活更轻松?
是的,这是一个我评估过的选项,但最终当前的设计获胜。在我看来,将此逻辑放入中间件的优势是更容易配置,但也存在一些缺点:首先,中间件需要检查请求路径是否是SAML端点之一,对于每个请求。其次,它将过于神奇,如果出现问题,则更难调试。
为什么不用 django-saml 这样的名字,就像许多其他Django应用程序一样?
遵循该模式,我应该使用 import saml 导入应用程序,但不幸的是,该模块名称已经在pysaml2中使用了。
更改
0.11.0 (2014-06-15)
Django 1.5自定义用户模型支持。感谢Jos van Velzen。
Django 1.5兼容性URL模板标签。感谢bula。
支持Django 1.5和1.6。感谢David Evans和Justin Quick。
0.10.0 (2013-05-05)
在重定向到循环之前检查RelayState是否为空。感谢Sam Bull报告此问题。
在全局注销过程中,当会话丢失时,向用户报告错误消息并执行本地注销。
0.9.2 (2013-04-19)
升级到pysaml2-0.4.3。
0.9.1 (2013-01-29)
向认证后端添加一个方法,以便可以根据SAML属性自定义授权。
0.9.0 (2012-10-30)
向认证后端添加一个信号,用于在更新_user方法保存用户之前修改用户。
0.8.1 (2012-10-29)
在将SAML属性设置为Django对象之前,如果它们太长,则对其进行修剪。这修复了与MySQL相关的崩溃。
0.8.0 (2012-10-25)
允许使用除“username”之外的不同属性来查找现有用户。
0.7.0 (2012-10-19)
添加一个设置,决定当用户尝试登录两次时,是否将用户重定向到下一个视图或显示授权错误。
0.6.1 (2012-09-03)
从我们的依赖中删除Django。
恢复对Django 1.3的支持。
0.6.0 (2012-08-29)
添加tox支持,配置为使用Python 2.6和2.7运行测试。
修复一些依赖项和sdist生成。作者:Lorenzo Gil
在设置中允许定义注销重定向URL。作者:Lorenzo Gil
添加一些日志调用以改进调试。作者:Lorenzo Gil
添加对自定义配置加载函数的支持。作者:Sam Bull。
当djangosaml2包含在Django项目中时,使测试更加健壮且易于运行。作者:Sam Bull。
在保存之前确保配置文件不为空。由Leif Johansson报告的bug。
0.5.0 (2012-05-22)
允许定义自定义配置加载器。它们可以根据请求动态变化。
不要自动添加身份验证后端。这样我们允许其他人添加他们自己的后端。
支持除映射到User模型之外的其他属性。这些属性存储在UserProfile模型中。
0.4.2 (2012-03-23)
修复idplist templatetag中关于使用旧pysaml2函数的崩溃。
添加对前述崩溃的测试。
0.4.1 (2012-03-19)
将pysaml2依赖项升级到版本0.4.1。
0.4.0 (2012-03-18)
将pysaml2依赖项升级到版本0.4.0(由于此升级,更新了我们的测试)。
添加日志调用以简化调试。
在pysaml2中使用Django配置的日志记录器。
0.3.3 (2012-02-14)
冻结pysaml2的版本,因为我们(目前!)与版本0.4.0不兼容。
0.3.2 (2011-12-13)
避免在读取映射到Django用户名的SAML属性时崩溃。
0.3.1 (2011-12-01)
在idplist templatetag的render方法中加载配置,使其更灵活和可递归。
0.3.0 (2011-11-30)
获取可用idps列表的模板标签。
允许将相同的SAML属性映射到多个Django字段。
0.2.4 (2011-11-29)
修复restructured text错误,导致pypi页面看起来不好。
0.2.3 (2011-06-14)
当用户首次创建时,设置一个不可用的密码。
0.2.2 (2011-06-07)
当用户已经登录且settings.LOGIN_REDIRECT_URL(不正确地)指向/saml2/login时,防止进入/saml2/login/端点时的无限循环。
0.2.1 (2011-05-09)
如果没有提供登录视图的下一个参数,则使用settings.LOGIN_REDIRECT_URL作为默认值。
0.2.0 (2011-04-26)
如果安装了elementtree库,则与Python 2.4兼容。
允许使用Django信号在身份验证阶段之后进行后处理。
0.1.1 (2011-04-18)
简单的视图,用于回显SAML属性。
改进文档。
更改创建新用户时的默认行为。现在它们的属性第一次填充。
允许在注销后设置下一页。
0.1.0 (2011-03-16)
从pysaml包中解放。