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 登录集成

  1. 用 admin 用户连接 LDAP 服务器;
  2. 根据用户 ID 或 email 查找用户 DN;
  3. 校验用户凭据。

示例代码

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

参考链接