在使用 Django 进行 Active Directory (AD) 身份验证时,django-auth-ldap
和 ldap3
都涉及到与 LDAP 服务器的交互。一个关键的差异在于如何处理与 LDAP 服务器的连接和身份验证。具体来说,django-auth-ldap
通常使用一个 绑定用户 (Bind DN) 来执行搜索和验证操作,而使用 ldap3
时,你有更多的灵活性,可以选择不同的绑定方式。
为什么需要绑定用户 (Bind DN)?
在 LDAP 身份验证过程中,绑定用户用于执行以下操作:
- 搜索用户信息:在用户登录时,系统需要在 LDAP 目录中查找用户的条目。为了执行这些搜索操作,通常需要一个具有读取权限的绑定用户。
- 验证用户凭据
:有两种主要方式来验证用户凭据:
- 服务账户绑定:使用预先配置的管理员账户(Bind DN 和密码)来搜索用户,然后尝试绑定用户的凭据。
- 直接用户绑定:直接使用用户提供的凭据尝试绑定,如果成功,则认证通过。
django-auth-ldap
中的 Bind DN
django-auth-ldap
通常使用一个 服务账户 来连接 LDAP 服务器并执行用户搜索。这就是为什么在 settings.py
中需要配置 AUTH_LDAP_BIND_DN
和 AUTH_LDAP_BIND_PASSWORD
。
# settings.py
import ldap
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
AUTH_LDAP_SERVER_URI = "ldap://your-ad-server.example.com"
AUTH_LDAP_BIND_DN = "cn=admin,dc=example,dc=com"
AUTH_LDAP_BIND_PASSWORD = "your-password"
AUTH_LDAP_USER_SEARCH = LDAPSearch(
"ou=users,dc=example,dc=com",
ldap.SCOPE_SUBTREE,
"(sAMAccountName=%(user)s)"
)
# 其他配置...
这里的 AUTH_LDAP_BIND_DN
和 AUTH_LDAP_BIND_PASSWORD
是用于绑定到 LDAP 服务器的服务账户凭据。这个账户需要有权限搜索用户和组信息。
ldap3
中的 Bind DN
使用 ldap3
时,绑定用户的配置取决于你的实现方式。在 ldap3
的自定义认证后端中,你有两种主要的绑定策略:
- 使用服务账户进行搜索:
- 先使用服务账户绑定,搜索用户信息。
- 然后使用用户提供的凭据进行绑定验证。
- 直接使用用户凭据进行绑定:
- 直接尝试使用用户提供的凭据进行绑定。
- 如果绑定成功,则认证通过;否则失败。
示例 1:使用服务账户进行搜索
在这种方法中,ldap3
会先使用服务账户绑定来搜索用户,然后再使用用户的凭据进行绑定验证。这与 django-auth-ldap
的方法类似。
# your_app/auth_backend.py
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.models import User
from ldap3 import Server, Connection, ALL, NTLM
class LDAPBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
if username is None or password is None:
return None
# LDAP服务器配置
LDAP_SERVER = 'ldap://your-ad-server.example.com'
LDAP_DOMAIN = 'DOMAIN'
LDAP_BASE_DN = 'DC=example,DC=com'
# 服务账户绑定
service_user = f'cn=admin,{LDAP_BASE_DN}'
service_password = 'your-password'
server = Server(LDAP_SERVER, get_info=ALL)
conn = Connection(server, user=service_user, password=service_password, authentication=NTLM)
if not conn.bind():
return None
# 搜索用户
conn.search(
search_base=LDAP_BASE_DN,
search_filter=f'(sAMAccountName={username})',
attributes=['cn', 'mail', 'givenName', 'sn']
)
if not conn.entries:
return None
user_entry = conn.entries[0]
# 使用用户凭据进行绑定验证
user_dn = user_entry.entry_dn
user_conn = Connection(server, user=user_dn, password=password, authentication=NTLM)
if not user_conn.bind():
return None
# 获取或创建Django用户
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = User(
username=username,
first_name=user_entry.givenName.value,
last_name=user_entry.sn.value,
email=user_entry.mail.value
)
user.set_unusable_password()
user.save()
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
在这个示例中,ldap3
首先使用服务账户 service_user
和 service_password
绑定到 LDAP 服务器,执行用户搜索。然后,使用找到的用户 DN 和用户提供的密码尝试绑定验证。
示例 2:直接使用用户凭据进行绑定
在某些情况下,你可以直接使用用户的凭据来绑定 LDAP 服务器进行验证。这种方法不需要预先配置服务账户,但有一些限制,例如无法同步用户信息,或者无法执行更复杂的查询操作。
# your_app/auth_backend.py
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.models import User
from ldap3 import Server, Connection, ALL, NTLM
class LDAPBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
if username is None or password is None:
return None
# LDAP服务器配置
LDAP_SERVER = 'ldap://your-ad-server.example.com'
LDAP_DOMAIN = 'DOMAIN'
LDAP_BASE_DN = 'DC=example,DC=com'
server = Server(LDAP_SERVER, get_info=ALL)
user_dn = f'DCN=DOMAIN,{LDAP_BASE_DN}' # 根据你的AD配置
# 直接使用用户凭据绑定
conn = Connection(server, user=f'{LDAP_DOMAIN}\\{username}', password=password, authentication=NTLM)
if not conn.bind():
return None
# 获取用户信息
conn.search(
search_base=LDAP_BASE_DN,
search_filter=f'(sAMAccountName={username})',
attributes=['cn', 'mail', 'givenName', 'sn']
)
if not conn.entries:
return None
user_entry = conn.entries[0]
# 获取或创建Django用户
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = User(
username=username,
first_name=user_entry.givenName.value,
last_name=user_entry.sn.value,
email=user_entry.mail.value
)
user.set_unusable_password()
user.save()
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
在这个示例中,ldap3
直接使用用户提供的凭据绑定到 LDAP 服务器进行验证。如果绑定成功,则认证通过。这种方法适用于简单的身份验证场景,但不适合需要更复杂的 LDAP 操作(如用户同步)的情况。
为什么在 ldap3
示例中没有显式配置绑定用户?
在之前的 ldap3
示例中,我的示例使用了用户提供的凭据进行绑定,而没有使用服务账户。这种情况下,确实不需要配置服务账户的 DN 和密码。但是,为了执行搜索操作(例如获取用户的详细信息),通常需要一个具有读取权限的绑定用户。
如果你需要执行用户搜索,建议使用服务账户进行初步绑定,然后再验证用户凭据。下面是一个更完整的 ldap3
示例,展示如何使用服务账户进行用户搜索和凭据验证。
完整示例:使用 ldap3
和服务账户进行身份验证
1. 安装 ldap3
确保已经安装了 ldap3
:
pip install ldap3
2. 创建自定义认证后端
在你的 Django 应用(例如 your_app
)中,创建 auth_backend.py
:
# your_app/auth_backend.py
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.models import User
from ldap3 import Server, Connection, ALL, NTLM
from django.conf import settings
class LDAPBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
if username is None or password is None:
return None
# LDAP服务器配置
LDAP_SERVER = 'ldap://your-ad-server.example.com'
LDAP_DOMAIN = 'DOMAIN'
LDAP_BASE_DN = 'DC=example,DC=com'
# 服务账户绑定
SERVICE_BIND_DN = "cn=admin,dc=example,dc=com"
SERVICE_BIND_PASSWORD = "your-password"
server = Server(LDAP_SERVER, get_info=ALL)
service_conn = Connection(server, user=SERVICE_BIND_DN, password=SERVICE_BIND_PASSWORD, authentication=NTLM)
if not service_conn.bind():
# 绑定失败,可能是服务账户信息错误
return None
# 搜索用户
service_conn.search(
search_base=LDAP_BASE_DN,
search_filter=f'(sAMAccountName={username})',
attributes=['cn', 'mail', 'givenName', 'sn']
)
if not service_conn.entries:
return None
user_entry = service_conn.entries[0]
user_dn = user_entry.entry_dn
# 使用用户凭据进行绑定验证
user_conn = Connection(server, user=user_dn, password=password, authentication=NTLM)
if not user_conn.bind():
return None
# 获取或创建Django用户
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = User(
username=username,
first_name=user_entry.givenName.value,
last_name=user_entry.sn.value,
email=user_entry.mail.value
)
user.set_unusable_password()
user.save()
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
3. 配置 settings.py
在 settings.py
中配置自定义认证后端:
# settings.py
INSTALLED_APPS = [
# 其他应用...
'your_app',
]
AUTHENTICATION_BACKENDS = (
'your_app.auth_backend.LDAPBackend', # 自定义LDAP认证后端
'django.contrib.auth.backends.ModelBackend',
)
# 其他必要配置...
4. 配置视图和权限
在 your_app/views.py
中配置认证视图,例如登录、登出和仪表板:
# your_app/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required, permission_required
def user_login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('dashboard')
else:
return render(request, 'your_app/login.html', {'error': 'Invalid credentials'})
return render(request, 'your_app/login.html')
@login_required
def dashboard(request):
return render(request, 'your_app/dashboard.html')
def user_logout(request):
logout(request)
return redirect('login')
@login_required
@permission_required('your_app.manage_users', raise_exception=True)
def manage_users(request):
from ldap3 import Server, Connection, ALL, NTLM
# LDAP服务器配置
LDAP_SERVER = 'ldap://your-ad-server.example.com'
SERVICE_BIND_DN = "cn=admin,dc=example,dc=com"
SERVICE_BIND_PASSWORD = "your-password"
LDAP_BASE_DN = 'DC=example,DC=com'
server = Server(LDAP_SERVER, get_info=ALL)
conn = Connection(server, user=SERVICE_BIND_DN, password=SERVICE_BIND_PASSWORD, authentication=NTLM)
if not conn.bind():
return render(request, 'your_app/error.html', {'message': '无法连接到 AD 服务器'})
# 查询所有用户
conn.search(
search_base=LDAP_BASE_DN,
search_filter='(objectClass=person)',
attributes=['cn', 'mail', 'givenName', 'sn']
)
users = conn.entries
return render(request, 'your_app/manage_users.html', {'users': users})
5. 创建模板
在 your_app/templates/your_app/
目录下创建 login.html
、dashboard.html
和 manage_users.html
。
之前的文章已经提供了这些模板的示例。
6. 定义权限
在 your_app/models.py
中定义自定义权限:
# your_app/models.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# 其他用户相关字段
class Meta:
permissions = [
("manage_users", "Can manage AD users"),
("manage_groups", "Can manage AD groups"),
]
同步数据库并在 Django Admin 中分配权限:
python manage.py makemigrations
python manage.py migrate
7. 分配权限
- 登录 Django Admin。
- 为需要管理 AD 用户的管理员用户分配
manage_users
权限:
- 进入用户详情页面。
- 在权限部分,选择
Can manage AD users
。
为什么 ldap3
配置中没有显式设置绑定用户?
在前面的 ldap3
示例中,如果仅展示了使用用户凭据直接绑定进行认证的情形,就不需要显式配置服务账户的 DN 和密码。
然而,实际应用中,为了执行用户搜索和获取用户信息,通常需要一个服务账户来绑定 LDAP 服务器。
因此,在更完整的实现中,一般会需要配置服务账户的 DN 和密码,正如 django-auth-ldap
中所做的那样。
关键区别总结
特性 |
|
|
类型 | Django 认证后端扩展 | 通用 LDAP 客户端库 |
依赖 |
| 纯 Python 实现,不需要编译 |
安装难度 | 高(尤其在 Windows 上) | 低,简单通过 |
配置复杂度 | 简单,通过 Django | 需要手动编写自定义认证后端 |
功能 | 提供现成的认证和用户同步功能 | 提供灵活的 LDAP 操作 API,适用于多种用途 |
跨平台兼容性 | 依赖 | 高,纯 Python 实现,跨平台兼容性好 |
灵活性 | 主要聚焦于认证和用户同步,灵活性有限 | 高,适用于各种自定义的 LDAP 交互需求 |
选择建议
- 选择
django-auth-ldap
:
- 如果你希望快速集成 LDAP 认证,并且能够成功安装其依赖项(如在 Linux 环境下)。
- 需要现成的功能,如用户同步和组权限映射,减少开发工作量。
- 选择
ldap3
:
- 如果你在 Windows 上开发,或者希望避免编译依赖问题。
- 需要更高的灵活性,能够根据项目需求自定义 LDAP 交互。
- 愿意编写自定义的 Django 认证后端,投入一些开发工作以实现所需功能。
进一步优化
无论你选择哪种方法,都可以根据项目需求进行优化:
- 安全性:
- 使用 LDAPS(LDAP over SSL)来加密与 LDAP 服务器的通信。
- 确保服务账户拥有最小必要权限。
- 性能:
- 对频繁的 LDAP 查询进行缓存,减少对 LDAP 服务器的负载。
- 优化搜索过滤器和基础 DN,提升查询效率。
- 用户同步:
- 定期同步 LDAP 用户到 Django 用户模型,保持数据一致性。
- 使用信号或后台任务(如 Celery)实现自动同步。
django-auth-ldap
和 ldap3
都是强大的工具,用于将 Django 应用与 LDAP 服务器集成。django-auth-ldap
提供了一个简便的解决方案,适合希望快速集成并拥有现成功能的项目,而 ldap3
则提供了更高的灵活性和跨平台兼容性,适合需要自定义 LDAP 交互的项目。