美文网首页
Django 借助 ldap3 自定义支持 LDAP 域账号的认

Django 借助 ldap3 自定义支持 LDAP 域账号的认

作者: rollingstarky | 来源:发表于2020-08-19 19:27 被阅读0次

    一、项目初始化

    • pip install django ldap3
    • django-admin startproject auth_demo
    • cd auth_demo
    • django-admin startapp authldap
    • python manage.py migrate

    二、编写自定义认证后端

    Django 的认证系统支持的自定义插件,本质上是一个实现了 get_user(user_id)authenticate(request, **credentials) 方法的类。
    其中 get_user 接收 user_id(可以是用户名、ID 等,但必须为 user 对象的主键)返回匹配的用户对象或 Noneauthenticate 接收 request 参数以及认证信息,根据最终的认证结果返回用户对象(认证通过)或 None(认证不通过)。

    auth_demo/authldap/authbackends.py

    # auth_demo/authldap/authbackends.py
    from django.contrib.auth.backends import BaseBackend
    from django.contrib.auth.models import User
    import ldap3
    
    # 替换为实际的域控 IP
    LDAP_HOST = 'xx.xx.xx.xx'
    
    
    class LdapBackend(BaseBackend):
        def authenticate(self, request, username=None, password=None):
            if ldap_auth(username, password):
                try:
                    user = User.objects.get(username=username)
                except User.DoesNotExist:
                    user = User(username=username)
                    if username.endswith('admin'):
                        user.is_staff = True
                        user.is_superuser = True
                    user.save()
                return user
            return None
    
        def get_user(self, user_id):
            try:
                return User.objects.get(pk=user_id)
            except User.DoesNotExist:
                return None
    
    
    # @example.com 改为自己域环境的域名
    def ldap_auth(username, password):
        username = username + '@example.com'\
            if '@' not in username else username
        server = ldap3.Server(LDAP_HOST, port=636, use_ssl=True)
        conn = ldap3.Connection(server, username, password)
        return conn.bind()
    

    代码中的 ldap_auth 函数用于执行 LDAP 认证,将 Django 收到的认证信息传递给 LDAP 服务器,通过 Connection 对象的 bind() 方法确认用户名密码是否正确。正确返回 True,不正确则返回 False

    LDAP 认证通过后,再检查 User 数据库表中是否已包含该用户。若该用户存在,则直接返回对应的 User 对象;若该用户不存在(第一次登录),则先在 User 中创建同名的新用户并添加权限等,保存后返回刚创建的 User 对象。

    假设所有需要添加 Django 管理员权限的账号名字都以 admin 结尾。。。

    配置认证后端

    Django 认证时使用的插件列表由 settings.py 中的 AUTHENTICATION_BACKENDS 字段指定,默认值为 ['django.contrib.auth.backends.ModelBackend']
    因此为了用上前面创建的自定义认证后端,需在 auth_demo/auth_demo/settings.py 配置文件中添加以下内容:

    AUTHENTICATION_BACKENDS = [
        'authldap.authbackends.LdapBackend',
        'django.contrib.auth.backends.ModelBackend'
        ]
    

    三、测试

    • 运行 python manage.py runserver 0.0.0.0:8000 启动 Web 服务
    • 在域中创建 testaccounttestaccount-admin 测试账号
    • 访问 http://xx.xx.xx.xx:8000/admin 进入 Django 后台,用测试账号登录

    效果如下:


    no staff staff & admin staff & admin

    四、初始化用户组

    首先梳理下之前代码的逻辑流程:

    • Django 后台获取前端传入的认证信息,并转发给 LDAP 服务器进行认证
    • 认证成功且该用户不在数据库中(首次登录),则创建对应的用户并将其返回;该用户已存在则直接返回
    • 创建用户时,若用户名以 admin 结尾,则额外向其添加 staff 权限和管理员权限
    • 认证失败返回 None

    假设在数据库中创建新用户时,需要根据一定的规则对用户的属组进行初始化(比如名称以 admin 结尾的用户自动添加到 admin 组中)。最终的代码如下:

    # auth_demo/authldap/authbackends.py
    from django.contrib.auth.backends import BaseBackend
    from django.contrib.auth.models import User, Group
    import ldap3
    
    # 替换为实际的域控 IP
    LDAP_HOST = 'xx.xx.xx.xx'
    
    
    class LdapBackend(BaseBackend):
        def authenticate(self, request, username=None, password=None):
            if ldap_auth(username, password):
                try:
                    user = User.objects.get(username=username)
                except User.DoesNotExist:
                    user = User(username=username)
                    user.save()
                    if username.endswith('admin'):
                        user.is_staff = True
                        user.is_superuser = True
                        add_group(user)
                        user.save()
                return user
            return None
    
        def get_user(self, user_id):
            try:
                return User.objects.get(pk=user_id)
            except User.DoesNotExist:
                return None
    
    
    # @example.com 改为自己域环境的域名
    def ldap_auth(username, password):
        username = username + '@example.com'\
            if '@' not in username else username
        server = ldap3.Server(LDAP_HOST, port=636, use_ssl=True)
        conn = ldap3.Connection(server, username, password)
        return conn.bind()
    
    
    def add_group(user, groupname='admin'):
        try:
            group = Group.objects.get(name=groupname)
        except Group.DoesNotExist:
            group = Group(name=groupname)
            group.save()
        group.user_set.add(user)
        group.save()
    

    删除上一步中数据库里新建的 testaccount-admin 账号,重新登录测试。则 LDAP 认证成功后,Django 后台会在数据库中新建 testaccount-admin 账号并将其添加至 admin 用户组中(如 admin 组不存在则新建该用户组)。即在新建账号的同时初始化其属组。

    groups

    参考资料

    Customizing authentication in Django

    相关文章

      网友评论

          本文标题:Django 借助 ldap3 自定义支持 LDAP 域账号的认

          本文链接:https://www.haomeiwen.com/subject/msjkjktx.html