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 = &aposEXAMPLE.COM&apos

与上面的 Email 验证例子类似的是,我们需要对 authenticate 方法进行重载,加入 kerberos 认证代码,python-kerberos 已经提供了 checkPassword 方法。

try:
    auth = kerberos.checkPassword(
        username, password, &apos&apos,
        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):
&quot&quot&quot
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 = &aposEXAMPLE.COM&apos
&quot&quot&quot
def authenticate(self, username=None, password=None):
    try:
        auth = kerberos.checkPassword(
            username, password, &apos&apos,
            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 = &apos%s@%s&apos % (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 里添加一个 &aposAuthorization&apos 段,并且加入 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 不太一样。 :-)

其实代码都已经写好,我们只需要处理一下拿到用户后的处理办法(&aposconfigure_user&apos 方法)和处理用户名的方法(&aposclean_username&apos 方法)就可以了。

我这里在拿到用户后,出于保护密码的原则,为该用户设置了一个无效密码(&aposuser.set_unusable_password()&apos 方法),并且设置了该用户的 Email。 同时,因为 RemoteUserBackend 默认返回的用户名是 ‘[username]@[KRB5_REALM]&apos,所以我也把后面的 REALM 给去掉,直接贴代码:

from django.conf import settings
from django.contrib.auth.backends import RemoteUserBackend

class ModAuthKerbBackend(RemoteUserBackend):
&quot&quot&quot
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/&ltproject&gt.conf should look like this:

&ltLocation &quot/&quot&gt

    SetHandler python-program
    PythonHandler django.core.handlers.modpython
    SetEnv DJANGO_SETTINGS_MODULE &ltproject&gt.settings
    PythonDebug On
&lt/Location&gt

&ltLocation &quot/auth/krb5login&quot&gt
    AuthType Kerberos
    AuthName &quot&ltproject&gt Kerberos Authentication&quot
    KrbMethodNegotiate on
    KrbMethodK5Passwd off
    KrbServiceName HTTP
    KrbAuthRealms EXAMPLE.COM
    Krb5Keytab /etc/httpd/conf/http.&lthostname&gt.keytab
    KrbSaveCredentials off
    Require valid-user
&lt/Location&gt

&quot&quot&quot
def configure_user(self, user):
    &quot&quot&quot
    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.
    &quot&quot&quot
    user.email = user.username + &apos@&apos + settings.KRB5_REALM.lower()
    user.set_unusable_password()
    user.save()
    return user

def clean_username(self, username):
    &quot&quot&quot
    Performs any cleaning on the &quotusername&quot 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
    &quot&quot&quot
    return username.replace(&apos@&apos + settings.KRB5_REALM, &apos&apos)<br></pre>

Django 是一个很强大的框架,虽然缺点和优点都同样的明显,有些甚至是由于 Python 语言或者类库造成的问题,但是因为其使用的便利性,高效的开发,而且其开发小组也非常活跃,使其特性的添加非常频繁,而且网上也有大量资源,例如 Django Snippets 网站,因此依然有着非常巨大优势。而通过阅读它的代码,往往都能获得更多启发。

链接:

[1] http://trac.calendarserver.org/browser/PyKerberos/

[2] http://www.djangosnippets.org/snippets/74/

版权所有丨转载请注明出处:https://kxq.io/archives/django下的kerberos登录