TP5.1默认Auth无法对接LDAP,因其设计依赖数据库查询用户数据并明文比对密码,而LDAP用户不在本地MySQL中、密码不可逆,硬同步会破坏单点登录且引发权限失效。

TP5.1 项目直接集成 LDAP 认证,必须绕过框架默认的数据库用户模型,改用 ldap_bind 做实时凭证校验——不能依赖 Auth 类的数据库查询逻辑,否则永远登不进 AD 用户。
为什么 TP5.1 默认 Auth 无法对接 LDAP
ThinkPHP 5.1 的 Auth 类设计初衷是查本地数据库表(如 think_auth_rule),它会调用 db()->where(...)->find() 获取用户数据,再比对密码。但 LDAP 用户根本不在你的 MySQL 里,密码也不可逆、不可明文比对。硬把 LDAP 用户写进数据库同步,既破坏单点登录一致性,又引入冗余和延迟。
常见错误现象:用户名正确、密码正确,但 login() 返回 false;或登录成功后 session('user') 为空;或能登录但权限控制失效。
- 根本原因:没切断
Auth的数据库依赖,仍试图从think_member表查 AD 用户 - 更隐蔽的问题:即使你伪造一条数据库记录,
Auth::check()仍会因缺少auth_group_access关联而判定无权限 - TP5.1 没内置 LDAP 支持,所有认证流程需手动接管
手动实现 LDAP 登录验证(核心三步)
跳过 Auth,在控制器中用原生 PHP ldap_connect + ldap_bind 完成“搜索 DN → 绑定校验”两阶段认证。这是企业 AD 环境最稳定的做法。
示例代码片段(放在 app/controller/Login.php 中):
// 1. 连接 LDAP 服务器
$ldap_conn = ldap_connect('ldap://ad.example.com', 389);
if (!$ldap_conn) {
$this->error('LDAP 服务器连接失败');
}
ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, LDAP_VERSION_3);
// 2. 先用管理员账号绑定(用于搜索)
$admin_dn = 'cn=admin,dc=example,dc=com';
$admin_pass = 'your_admin_password';
if (!ldap_bind($ldap_conn, $admin_dn, $admin_pass)) {
$this->error('LDAP 管理员绑定失败');
}
// 3. 根据用户名搜索对应 DN(注意:AD 通常用 sAMAccountName,非 uid)
$search = ldap_search($ldap_conn, 'dc=example,dc=com', '(sAMAccountName='.$username.')');
$entries = ldap_get_entries($ldap_conn, $search);
if ($entries['count'] !== 1) {
$this->error('用户不存在或匹配多个');
}
$user_dn = $entries[0]['dn'];
// 4. 用该 DN 和用户密码再次 bind(这才是真正的身份校验)
if (!ldap_bind($ldap_conn, $user_dn, $password)) {
$this->error('用户名或密码错误');
}
// 5. 校验通过,写入 session(注意:不要写 password 字段)
session('ldap_user', [
'username' => $username,
'fullname' => $entries[0]['displayname'][0] ?? $username,
'email' => $entries[0]['mail'][0] ?? '',
]);
$this->success('登录成功', url('index/index'));
- 关键点:
sAMAccountName是 AD 默认登录名字段,不是uid;若用 OpenLDAP 则可能为uid,需按实际 schema 调整 filter - 务必设置
LDAP_OPT_PROTOCOL_VERSION为 3,否则 Windows AD 会拒绝连接 - 不要在 session 中存密码,也不要调用
Auth::login()—— 它会触发 DB 查询 - 如果 AD 启用了 SSL(端口 636),则
ldap_connect()参数应为'ldaps://ad.example.com',且需确保 PHP 启用openssl扩展
登录后如何做权限控制(替代 Auth)
LDAP 本身不提供 RBAC 权限数据,TP5.1 的 Auth 规则表也管不了 AD 用户。必须自己建映射关系,且只查一次、缓存住。
推荐做法:在用户首次登录成功后,根据其所属 AD 组(如 memberOf 属性)映射到本地权限组 ID,存入 think_ldap_group_map 表或 Redis 缓存:
// 登录成功后追加这段
$groups = $entries[0]['memberof'] ?? [];
$role_ids = [];
foreach ($groups as $group_dn) {
// 提取 CN,例如 "CN=IT-Admins,CN=Groups,DC=example,DC=com" → "IT-Admins"
if (preg_match('/CN=([^,]+)/i', $group_dn, $m)) {
$role_id = db('auth_group')->where('title', $m[1])->value('id');
if ($role_id) $role_ids[] = $role_id;
}
}
session('role_ids', array_unique($role_ids));
- 后续权限判断直接读
session('role_ids'),避免每次请求都查 LDAP - 前端菜单/按钮级控制,可用
in_array($rule_id, session('role_ids'))简单判断 - 若需动态刷新权限(如 AD 组变更),加个后台“同步 LDAP 组”按钮,重新跑一遍映射逻辑即可
- 切勿在中间件里反复调用
ldap_search—— 性能灾难,且容易触发 AD 锁定策略
SSL/TLS 和超时问题最容易被忽略
生产环境启用 LDAPS 或 STARTTLS 后,PHP 默认不校验证书,但 AD 往往强制校验;不处理会导致 ldap_bind(): Can't contact LDAP server 或静默失败。
- 解决 LDAPS 连接失败:在
ldap_connect()前加putenv('LDAPTLS_REQCERT=never');(仅测试环境),生产环境应部署 CA 证书并设置LDAPTLS_CACERT=/path/to/ca.crt - STARTTLS 方式(端口 389)需在管理员 bind 后立即调用
ldap_start_tls($ldap_conn),否则后续 bind 会降级为明文 - LDAP 查询默认无超时,网络抖动时整个页面卡死。务必设置:
ldap_set_option($ldap_conn, LDAP_OPT_NETWORK_TIMEOUT, 5); - AD 默认限制每分钟搜索次数(如 300 次),高频并发登录需加 Redis 限流,或在负载层前置统一认证网关(如 Keycloak)
真正难的不是连上 LDAP,而是让 TP5.1 放弃它习惯的“查库→比密→赋权”路径,转而接受“远程校验→本地映射→缓存决策”这套新链路。任何试图复用 Auth 类又强行注入 LDAP 数据的方案,都会在权限继承、token 刷新、登出清理等环节暴露裂痕。


















