一、项目初始化
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 对象的主键)返回匹配的用户对象或 None
;authenticate
接收 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 服务 - 在域中创建
testaccount
和testaccount-admin
测试账号 - 访问 http://xx.xx.xx.xx:8000/admin 进入 Django 后台,用测试账号登录
效果如下:
四、初始化用户组
首先梳理下之前代码的逻辑流程:
- 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
组不存在则新建该用户组)。即在新建账号的同时初始化其属组。
参考资料
Customizing authentication in Django