Django 1.6中的PySAML2集成
项目描述
djangosaml2-dj16
djangosaml2是Django 1.6应用程序,它将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》指向一个包含属性映射的目录,这些映射用于将用户属性名称从多个标准翻译过来。通常情况下,只需复制源分发版中《tests/attributemaps》目录下的默认 PySAML2 属性映射即可,这是安全的。
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 将尝试在模型中存储属性,如果该模型中有匹配的字段。否则,它将尝试使用您的自定义配置文件模型执行相同操作。
有关 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将知道是否需要保存用户对象,因此您不需要这样做,并且不再发出对save方法的调用。
IdP设置
恭喜您,您已经完成了联盟的SP端配置。现在,您需要将这个新SP的实体ID和元数据发送给IdP管理员,以便他们将其添加到他们信任的服务列表中。
您可以通过启动Django开发服务器并访问http://localhost:8000/saml2/metadata URL来获取这些信息。如果您已将djangosaml2 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中间件类中,让我们的生活更轻松?
是的,这是一个我确实评估过的选项,但最终当前的设计获胜。在我看来,将此逻辑放入中间件的优势是使其更容易配置,但有几个缺点:首先,中间件需要检查对于每个请求,请求路径是否为SAML端点之一。其次,它将过于神奇,如果在出现问题时,调试将更加困难。
为什么不将此包命名为django-saml,像许多其他Django应用程序一样?
按照该模式,我应该用import saml导入应用程序,但不幸的是,该模块名称已在pysaml2中使用。
更改
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)
在身份验证后端的update_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
在保存之前确保配置文件不是None。问题报告者:Leif Johansson
0.5.0 (2012-05-22)
允许定义自定义配置加载器。它们可以根据请求动态。
不要自动添加认证后端。这样我们允许其他人添加他们自己的后端。
支持除映射到用户模型之外的其他属性。这些属性存储在UserProfile模型中。
0.4.2 (2012-03-23)
修复idplist模板标签中关于使用旧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模板标签的render方法中加载配置,使其更灵活且可重入。
0.3.0 (2011-11-30)
获取可用IDP列表的模板标签。
允许将相同的SAML属性映射到多个Django字段。
0.2.4 (2011-11-29)
修复restructured text错误,这些错误使pypi页面看起来很糟糕。
0.2.3 (2011-06-14)
在首次创建用户时设置不可用的密码。
0.2.2 (2011-06-07)
当用户已登录且设置.LOGIN_REDIRECT_URL(不好)指向/saml2/login时,防止进入/saml2/login/端点时的无限循环。
0.2.1 (2011-05-09)
如果没有提供next参数给登录视图,则使用设置.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包中解放。