LDAP 概述
- LDAP 数据结构为树形结构;
- 读性能优异,写性能较差;
- LDAP 是开放标准,协议跨平台。
Entry
条目(Entry)是 LDAP 的基本单位。
- DN(Distinguished Name):唯一标识名,如
cn=baby,ou=marketing,dc=mydomain,dc=org - RDN:相对唯一标识名,如
cn=baby - Base DN:根目录,如
dc=mydomain,dc=org - DC:Domain Component
- OU:Organization Unit
- CN:Common Name
- UID:User ID
- O:Organization
- C:Country
ObjectClass
对象类是属性集合,定义条目类型。可继承多个对象类获得不同属性。
- 结构类型(Structural):规定实体基本属性,每个条目仅属于一个结构型对象类;
- 抽象类型(Abstract):组织共性属性,作为模板,条目不能直接继承;
- 辅助类型(Auxiliary):扩展属性。
Schema
Schema 包含对象类、属性类型和语法,约定条目、属性和值的关系。
搭建 LDAP 测试服务器
用 Docker 安装 OpenLDAP 和 LAM(LDAP Account Manager)。
创建测试网络
docker network create --subnet=172.18.0.0/24 ldap
搭建 OpenLDAP 服务
docker run -p 127.0.0.1:389:389 -p 127.0.0.1:636:636 \
--env LDAP_ORGANISATION="dota2" --env LDAP_DOMAIN="dota2.com" \
--env LDAP_ADMIN_PASSWORD="admin@123" \
--name ldap \
--network ldap \
--ip 172.18.0.2 \
-d osixia/openldap
搭建 LAM 服务
docker run -p 127.0.0.1:1810:80 \
--name lam \
--ip 172.18.0.3 \
--network ldap \
-d ldapaccountmanager/lam
LAM 配置
访问 http://localhost:1810,配置服务器地址为 ldap://172.18.0.2:389,Tree suffix 为 dc=dota2,dc=com,管理员 DN 为 cn=admin,dc=dota2,dc=com。
Python 实现 LDAP 登录集成
- 用 admin 用户连接 LDAP 服务器;
- 根据用户 ID 或 email 查找用户 DN;
- 校验用户凭据。
示例代码
from ldap3 import Connection, Server
from ldap3.core.exceptions import LDAPBindError
LDAP_SERVER = 'localhost'
LDAP_PORT = 389
USE_SSL = False
ADMIN_DN = 'cn=admin,dc=dota2,dc=com'
ADMIN_PASSWORD = 'admin@123'
SEARCH_DOMAIN = 'ou=dev,dc=dota2,dc=com'
def login_user(usermail, password):
server = Server(LDAP_SERVER, LDAP_PORT, USE_SSL)
with Connection(server, ADMIN_DN, ADMIN_PASSWORD, auto_bind=True) as conn:
found = conn.search(
SEARCH_DOMAIN,
f'(mail={usermail})',
attributes=['cn', 'telephoneNumber'],
)
if not found:
return False
entry = conn.entries[0]
try:
with Connection(server, entry.entry_dn, password, auto_bind=True) as conn:
pass
except LDAPBindError:
return False
entry_data = entry.entry_attributes_as_dict
return {
'username': entry_data['cn'][0] if entry_data['cn'] else None,
'phone': entry_data['telephoneNumber'][0] if entry_data['telephoneNumber'] else None,
'email': usermail,
}
if __name__ == "__main__":
assert login_user('[email protected]', 'test') is True
assert login_user('[email protected]', 'test123') is False