杂学

Python对接LDAP/AD的过程详解

2023-02-13  本文已影响0人  何小有

不同公司的 LDAP/AD 服务配置各不相同,很难封装一个通用的方法,所以我们在对接 LDAP/AD 的过程中,需要了解自己公司的 LDAP/AD 服务配置是怎么样的,才能写出正确的对接代码,因此下面将拆解过程并提供相关的文档地址。

首先需要了解一些 LDAP/AD 的基本概念:

Python 对接 LDAP 目前主要有两个库,ldap3python-ldap 库:

库名称 实现语言 接口风格
ldap3 纯Python 偏向对象
python-ldap 混合C+Python 偏向过程

综上对比,推荐使用 ldap3 实现 LDAP 对接:

pip install ldap3

首先通过 PIP 安装 ldap3 库,并导入相关类到代码中:

from ldap3 import Server, Connection, ALL

通过 LDAP 服务器地址创建一个 LDAP 服务对象:

server = Server('127.0.0.1', get_info=ALL)
print(server)
# Server(host='127.0.0.1', port=389, use_ssl=False, allowed_referral_hosts=[('*', True)], get_info='ALL', mode='IP_V6_PREFERRED')

观察 LDAP 服务对象的输出信息:

输出信息 含义
host='127.0.0.1' LDAP 服务器 IP 或 URL
port=389 服务端口,默认就是 389 端口
use_ssl=False 是否使用 SSL,如果为 True,意味需要 建立安全连接
allowed_referral_hosts=[('*', True)] 限定允许请求的主机
get_info='ALL' 是否必须读取服务器架构和服务器特定信息
mode='IP_V6_PREFERRED' 用于解析 DNS 中的 LDAP 服务器名称的双 IP 堆栈行为

更详细的配置及其含义可以查看 LDAP 服务对象 (server-object) 文档。

使用 LDAP 服务对象,基于一个公用账号(使用公有账号可以确保服务稳定)建立 LDAP 连接:

conn = Connection(server, user='Domain\\User', password='password', auto_bind=True, raise_exceptions=True)
print(conn)
# Connection(server=Server(host='127.0.0.1', port=389, use_ssl=False, allowed_referral_hosts=[('*', True)], get_info='ALL', mode='IP_V6_PREFERRED'), user='Domain\\User', password='password', auto_bind='NO_TLS', version=3, authentication='SIMPLE', client_strategy='SYNC', auto_referrals=True, check_names=True, read_only=False, lazy=False, raise_exceptions=False, fast_decoder=True, auto_range=True, return_empty_attributes=True, auto_encode=True, auto_escape=True, use_referral_cache=False)

观察 LDAP 连接对象的输出信息:

输出信息 含义
user='Domain\User' 绑定的用户的帐户
password='password' 绑定的用户密码
auto_bind='NO_TLS' 自动打开并绑定连接
version=3 LDAP 协议版本
authentication='SIMPLE' 身份验证方法
client_strategy='SYNC' 客户端使用的通信策略
auto_referrals=True 指定连接是否服务器中允许的
check_names=True 搜索结果将按照结构中指定的格式进行格式化
read_only=False True 时禁止修改、删除、添加等操作
lazy=False True 时连接将延迟打开和绑定,直到请求另一个 LDAP 操作
raise_exceptions=False True 时引发 LDAPOperationResult 的异常
fast_decoder=True False 时使用 pyasn1 解码器而不是内部解码器

更详细的配置及其含义可以查看 LDAP 连接对象 (Connection) 文档。

到这一步的时候,可以询问 LDAP 服务器当前连接用户是谁?简单验证一下连接有效性:

conn.extend.standard.who_am_i()
# 'u:Domain\\User'

使用公用账号查询某个用户的 SAMAccountName 信息是否存在:

result = conn.search(search_base='OU=OU,DC=Domain,DC=LOCAL', search_filter='(sAMAccountName=xiaoming)')
# True

观察 LDAP 连接对象的 search() 函数输入/输出信息:

如果上一步的用户查询成功,即结果为 True,下面就可以查看其 response 信息,获取查询到的用户详细信息:

conn.response
# [{'raw_dn': b'CN=\xbd...,DC=LOCAL', 'dn': 'CN=小明-10001001,OU=自动化测试组,OU=测试部,OU=研发中心,OU=Domain,OU=行政组织,OU=OU,DC=LEEDARSON,DC=LOCAL', 'raw_attributes': {}, 'attributes': {}, 'type': 'searchResEntry'}]

上面返回的用户 dn 信息有两种:

不管是那种格式,信息本身的内容是一样的。默认情况下使用 user_dn = conn.response[0]['dn'] 获取用户 dn 信息就可以。

接下来就使用用户 dn 信息去验证用户的密码是否正确,如果密码正确,就和前面公用账号登录一样可以获取用户信息。如果登录异常,我们可以根据响应内容判断具体异常的原因:

from ldap3.core.exceptions import LDAPInvalidCredentialsResult

try:
    Connection(server, user=user_dn, password='password', auto_bind=True, raise_exceptions=True)
except LDAPInvalidCredentialsResult as e:
    if '52e' in e.message:
        print('账号密码不正确')
    elif '775' in e.message:
        print('账号已锁定,请联系管理员或等待自动解锁')
    elif '533' in e.message:
        print('账号已禁用')
    else:
        print('认证失败,请联系管理员检查该账号')

更多具体的情况,就需要实际对接公司的 LDAP/AD 服务时,才会遇到了。

上一篇下一篇

猜你喜欢

热点阅读