Django 下的 Kerberos 登录
Kerberos 这种统一用户名和密码进行登录的方式在各大公司(尤其外企)内部应该都得到广泛应用,它以其安全、高效和易于管理等特性得到了很多系统管理员的喜爱。
目前网上对于 Kerberos 登录原理的描述都过于复杂,其实它的实现非常简单,当你向一个部署了 Kerberos 的应用服务器发起登录请求的时候,服务器会去 /etc/krb5.conf 里描述的 KDC 服务器用 Kerberos 协议发起一个登录请求,如果用户名密码验证通过,将会向服务器发一个票(Ticket),否则将会引出一个错误。然后服务器可以将票发给客户端,以后客户端就可以用这张票进行其它操作。与火车票和电影票一样,Kerberos 的票,也是有使用时间限制的,如果不经过特殊设置,这张票的超时时间大约是 6 个小时。
而在 Django 里使用 Kerberos 登录,有两种办法,一种是由 Django 直接向 KDC 验证密码,另一种是在 Apache 上使用 mod_auth_kerb 模块,由浏览器来处理登录请求。
这两种办法其实都是对 Django Auth Backend 的重载,所有的 Auth Backend 都位于 django/contrib/auth/backends.py 里,这里[2]也有一个使用 Email 来进行验证的范例,我受此启发,写了这两个例子,希望也能抛砖引玉,能给你们更多启发。
第一种 - 由 Django 直接向 KDC 验证密码:
这种办法比较简单,需要在 web server 上装好 python-kerberos 包,并且配置好 /etc/krb5.conf,详细的配置方法,最好咨询 IT 部门,配置成功后在服务器上用 Kerberos 上有的普通用户运行 kinit,如果能够密码验证通过就行。
并且在 Django 的 settings.py 里写入类似下面这行,定义 Kerberos 的 Realm:
# Kerberos settings KRB5_REALM = 'EXAMPLE.COM'
与上面的 Email 验证例子类似的是,我们需要对 authenticate 方法进行重载,加入 kerberos 认证代码,python-kerberos 已经提供了 checkPassword 方法。
try: auth = kerberos.checkPassword( username, password, '', settings.KRB5_REALM ) except kerberos.BasicAuthError, e: return None
完整代码如下:
import kerberos from django.conf import settings from django.contrib.auth.backends import ModelBackend from django.contrib.auth.models import User class KerberosBackend(ModelBackend): """ Kerberos authorization backend for TCMS. Required python-kerberos backend, correct /etc/krb5.conf file, And correct KRB5_REALM settings in settings.py. Example in settings.py: # Kerberos settings KRB5_REALM = 'EXAMPLE.COM' """ def authenticate(self, username=None, password=None): try: auth = kerberos.checkPassword( username, password, '', settings.KRB5_REALM ) except kerberos.BasicAuthError, e: return None try: user = User.objects.get(username=username) except User.DoesNotExist: user = User.objects.create_user( username = username, email = '%s@%s' % (username, settings.KRB5_REALM.lower()) ) user.set_unusable_password() user.save() return user
第二种:Apache 上使用 mod_auth_kerb:
这种方法略有复杂,部署它需要向 KDC 申请一个 keytab 文件,以授权该 Web Server 向 KDC 发起请求,并且需要安装和配置 mod_auth_kerb(很简单,后面有),并且 /etc/krb5.conf 一点也不能少。
但是好处也是非常明显的,上面那种依然是使用 Django Auth Contrib 的 Session Manager 来负责登录信息的维持,但是这种方法将能够完全使用 Kerberos 自身提供的 Features,包括登录维持,和 kinit 的支持,也就是说,只要在本机上使用 kinit 成功登录过一次,用 Firefox (目前似乎在 Linux 上只支持该浏览器)访问部署了 mod_auth_kerb 的网站,将都不再需要登录。
它的原理包括两种条件,一种是没有在本机执行 kinit 的,使用 Firefox 直接访问服务器,服务器将会返回一个 401 Authorization Required 错误,这时 Firefox 会弹出对话框询问你的 Kerberos 用户名和密码,并提交你的密码。另一种在本机已经执行过 kinit 的,Firefox 会去读取你客户端的 Kerberos ticket 缓存,如果没有过期的话,就会使用它。无论哪种办法,Firefox 都将在 HTTP Header 里添加一个 'Authorization' 段,并且加入 Basic Authorization 验证方式,例如我本机上的:
Authorization Basic eGt1YW5nOkxvdmVvZnJvYWQuMTIz
使用这种部署方式,在 Django 1.1 版本以下,还没有比较好的解决办法,但好在 Django 1.1 提供了 RemoteUserBackend 后端,依然在 django/contrib/auth/backends.py 路径里,通过阅读它的代码,我们可以看到它其实依然是个 ModelBackend 的继承,而 Django 的 Request Handler 已经默认将 HTTP Meta 里的 REMOTE_USER 段给加入处理范围之内了,因此 RemoteUserBackend 的 ’authenticate‘ 与 ModelBackend 不太一样。 :-)
其实代码都已经写好,我们只需要处理一下拿到用户后的处理办法('configure_user' 方法)和处理用户名的方法('clean_username' 方法)就可以了。
我这里在拿到用户后,出于保护密码的原则,为该用户设置了一个无效密码('user.set_unusable_password()' 方法),并且设置了该用户的 Email。 同时,因为 RemoteUserBackend 默认返回的用户名是 ‘[username]@[KRB5_REALM]',所以我也把后面的 REALM 给去掉,直接贴代码:
from django.conf import settings from django.contrib.auth.backends import RemoteUserBackend class ModAuthKerbBackend(RemoteUserBackend): """ mod_auth_kerb modules authorization backend for TCMS. Based on DjangoRemoteUser backend. Required correct /etc/krb5.conf, /etc/krb5.keytab and Correct mod_auth_krb5 module settings for apache. Example apache settings: # Set a httpd config to protect krb5login page with kerberos. # You need to have mod_auth_kerb installed to use kerberos auth. # Httpd config /etc/httpd/conf.d/<project>.conf should look like this: <Location "/"> SetHandler python-program PythonHandler django.core.handlers.modpython SetEnv DJANGO_SETTINGS_MODULE <project>.settings PythonDebug On </Location> <Location "/auth/krb5login"> AuthType Kerberos AuthName "<project> Kerberos Authentication" KrbMethodNegotiate on KrbMethodK5Passwd off KrbServiceName HTTP KrbAuthRealms EXAMPLE.COM Krb5Keytab /etc/httpd/conf/http.<hostname>.keytab KrbSaveCredentials off Require valid-user </Location> """ def configure_user(self, user): """ Configures a user after creation and returns the updated user. By default, returns the user unmodified. Here, the user will changed to a unusable password and set the email. """ user.email = user.username + '@' + settings.KRB5_REALM.lower() user.set_unusable_password() user.save() return user def clean_username(self, username): """ Performs any cleaning on the "username" prior to using it to get or create the user object. Returns the cleaned username. For more info, reference clean_username function in django/auth/backends.py """ return username.replace('@' + settings.KRB5_REALM, '')
Django 是一个很强大的框架,虽然缺点和优点都同样的明显,有些甚至是由于 Python 语言或者类库造成的问题,但是因为其使用的便利性,高效的开发,而且其开发小组也非常活跃,使其特性的添加非常频繁,而且网上也有大量资源,例如 Django Snippets 网站,因此依然有着非常巨大优势。而通过阅读它的代码,往往都能获得更多启发。
链接: