OpenLDAP 统一认证实战:给自建服务做集中登录、ACL 锁死密码属性与 TLS 加密

一个站长迟早会遇到的麻烦:账号太多

刚开始做站的时候,你可能只有一两个后台:博客后台、服务器 SSH。随着时间推移,账号会像滚雪球一样多起来——Typecho 后台一个、phpMyAdmin 一个、监控面板一个、Git 服务一个、网盘一个、内网的各种小工具各一个。每个都要设密码,每个都要记,改一次密码要改一圈,删一个人要把所有系统挨个清一遍。

这就是「身份散落」的问题。解决办法在运维领域早有标准答案:把认证这件事从各个应用里抽出来,交给一个统一的目录服务,应用只负责问「这个人是谁、能不能进」,而不再自己存密码。这套机制在开源世界最主流的实现就是 LDAP(Lightweight Directory Access Protocol),具体到自建场景,通常是 OpenLDAP。

对个人站长来说,这不是「企业才需要」的复杂东西。一台 1 核 1G 的小机器就能跑起 OpenLDAP,装一次,之后每接一个新服务都只是填几个配置字段的事。更关键的是,它把「谁能进」从散落各处变成了一处管理,安全边界一下就清晰了。

LDAP 不是数据库,是目录

理解 LDAP 最大的障碍是把它当成关系型数据库。它不是。LDAP 是「目录服务」,专门为「大量读取、少量写入」的树状数据设计。它的数据结构叫 DIT(Directory Information Tree),很像一棵倒过来的文件系统:

dc=example,dc=com            (根,你的域名)
├── ou=people                (组织单元:人)
│   ├── uid=alice
│   └── uid=bob
├── ou=groups                (组织单元:组)
│   ├── cn=admins
│   └── cn=devs
└── ou=services              (组织单元:服务账号)

每个节点叫一个「条目(entry)」,每个条目有若干「属性(attribute)」,以及一个唯一的「专有名称(DN)」,比如 uid=alice,ou=people,dc=example,dc=com。你以后每次配置应用,要填的都是 DN 和属性名,理解了这一层,后面的配置就全是填空题。

把域名拆成 dc= 部分是 LDAP 的惯例。dc 是 domain component 的缩写,ou 是 organizational unit,cn 是 common name,uid 是 user id。这些缩写看着陌生,用两次就熟了。

安装 OpenLDAP 服务端

Debian/Ubuntu 下安装 slapd(OpenLDAP 的服务端进程)和配套工具:

DEBIAN_FRONTEND=noninteractive apt install -y slapd ldap-utils

# 查看是否自动生成了管理员密码
cat /etc/ldap/slapd.d/*   # 配置以目录形式存储在 slapd.d

安装过程会引导你设置管理员密码。如果没设或忘了,可以用下面这条命令重设(它会重新配置并允许你输入新密码):

dpkg-reconfigure slapd

重新配置时会问几个问题,这里逐条说清楚:

  • Omit OpenLDAP server configuration? 选 No,我们要配置。
  • DNS domain name:填你的域名,比如 example.com。它会自动转成 dc=example,dc=com。
  • Organization name:随意,填站点名即可。
  • Administrator password:设置一个强密码,这是 cn=admin,dc=example,dc=com 的密码。
  • Database backend:选 MDB。这是现代 OpenLDAP 的默认后端,比老的 BDB 快且稳定。
  • Remove database when slapd is purged? 选 No,避免误卸载时丢数据。

装好后确认服务在跑:

systemctl status slapd
ldapsearch -x -H ldap://localhost -b dc=example,dc=com -D cn=admin,dc=example,dc=com -W

最后这条命令会提示输入管理员密码,然后返回目录树内容。能返回结果,说明服务端一切正常。

建组织结构:把人分进 OU

装完的库是空的,先建组织单元。写一个 LDIF 文件(LDAP 专用的数据交换格式),然后导入:

# base.ldif
dn: ou=people,dc=example,dc=com
objectClass: organizationalUnit
ou: people

dn: ou=groups,dc=example,dc=com
objectClass: organizationalUnit
ou: groups

导入命令:

ldapadd -x -H ldap://localhost -D cn=admin,dc=example,dc=com -W -f base.ldif

然后建第一个用户。这里用的是 inetOrgPerson 对象类,它是 LDAP 里表示「人」的标准类,邮箱、姓名、电话这些属性都来自它:

# alice.ldif
dn: uid=alice,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: alice
cn: Alice Zhang
sn: Zhang
givenName: Alice
mail: alice@example.com
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/alice
loginShell: /bin/bash
userPassword: 一个强密码

注意 uidNumber/gidNumber/homeDirectory 来自 posixAccount 类。如果你只打算用它做 Web 应用的认证,可以不要这几个属性、去掉那三个 objectClass;但只要你想让 Linux 系统用户也走 LDAP 登录(后面会讲),这几个字段就是必需的。

导入用户后立刻验证一下能不能用:

ldapwhoami -x -H ldap://localhost -D uid=alice,ou=people,dc=example,dc=com -W

返回 dn:uid=alice,... 就说明这个账号的密码设置正确、能绑定成功。

密码不能明文存:改成加密存储

上面 LDIF 里的 userPassword 是明文的,这在 LDAP 里属于大忌——因为只要有人读得到这个属性,密码就泄露了,而默认 ACL 下很多账号是可读的。正确的做法是存密码的哈希。

先生成哈希:

slappasswd -h '{SSHA}' -s '你的密码'
# 输出 {SSHA}xxxxxxxx...,这就是可入库的哈希

然后把 LDIF 里的 userPassword 换成这个哈希值。这样即使目录被读,拿到的也只是不可逆的摘要。SSHA 是加盐的 SHA-1,兼容性最好;如果你的环境支持,也可以用 {ARGON2} 或 {SHA512},安全性更高。

更重要的是收紧 ACL。默认配置往往允许匿名读取部分属性,尤其是 userPassword。在 slapd 的配置里,必须保证 userPassword 只对自己和管理员可读。这条规则在 Debian 的 slapd.conf 时代写在文本配置里,现在配置迁移到了 /etc/ldap/slapd.d/ 目录下,要改得用 ldapmodify 操作 olcAccess 属性。一个典型的安全 ACL 大致是:

olcAccess: {0}to attrs=userPassword
  by self write
  by anonymous auth
  by * none
olcAccess: {1}to *
  by self read
  by users read
  by * none

第一段的意思是:密码属性只有本人可改、匿名只能在认证(bind)时使用、其他所有人一律读不到。第二段允许已登录用户读其他属性。这两条是 LDAP 安全的地基,配置新库时务必先立起来。

让 Web 应用走 LDAP 认证

统一认证真正的价值在于「接应用」。以常见的自建服务为例,配置里通常只有这四个字段:

  • LDAP 服务器地址:ldap://127.0.0.1:389(本机)或 ldaps://ldap.example.com:636(加密)。
  • 基础 DN(Base DN):ou=people,dc=example,dc=com,告诉应用从哪棵子树开始找用户。
  • 绑定 DN(Bind DN):一个能读目录的服务账号,比如 cn=admin,dc=example,dc=com,或专门建的只读账号。
  • 用户名字段(Filter):告诉应用拿登录框里输入的用户名去匹配哪个属性,常见是 (&(objectClass=inetOrgPerson)(uid=%s)) 或 (mail=%s)。

认证流程分两步:应用先用 Bind DN 连上目录,按用户名字段搜到对应用户的 DN;再拿这个 DN 和用户输入的密码做一次 bind(绑定)。bind 成功,即密码正确;失败,即认证不通过。应用从头到尾拿不到密码原文,它只把密码转发给 LDAP 做校验——这正是统一的妙处。

这里有个容易踩的坑:Bind DN 这个服务账号通常需要有搜索全局的权限,如果只是给应用用,最好单独建一个只读账号,而不是用 cn=admin。用管理员账号会让每个应用的配置文件里都躺着一个超级凭据,一旦某个应用被攻破,整个目录就失守了。

加密:别再让密码裸奔

标准的 LDAP 协议(389 端口)是明文的,包括 bind 时传的密码。在公网上这么用,等于把密码写在明信片上寄出去。两个解决方案:

方案一:LDAPS(636 端口)。用 TLS 把整个连接包起来。需要给 slapd 准备好证书,并在配置里指定 olcTLSCertificateFile 和 olcTLSCertificateKeyFile,然后客户端连 ldaps:// 地址。这是最直接的做法,推荐用在跨机器访问的场景。

方案二:StartTLS。仍然连 389 端口,但在建立连接后通过 StartTLS 指令升级为加密。它比 LDAPS 更灵活,端口不需要额外开放。

另外,别以为「都在内网」就不必加密。内网里任何一个被攻破的低权限节点,都可能去嗅探明文 LDAP 流量。给 389 端口加上 TLS,是几乎零成本的加固。配置好后用这条命令验证加密是否生效:

ldapsearch -x -ZZ -H ldap://localhost -b dc=example,dc=com \
  -D cn=admin,dc=example,dc=com -W

-ZZ 的含义是「必须成功升级为 TLS,否则报错退出」。如果这条命令报证书错误,说明 TLS 还没配好,那就继续排查,别用 -Z(允许降级)蒙混过去。

可选进阶:让 Linux 系统登录也走 LDAP

如果你想让「一套账号登录 SSH 和后台」,可以装 libnss-ldapd 和 libpam-ldapd,把系统的 NSS 和 PAM 接到 LDAP 上。这样 uid=alice 就能直接 SSH 登录服务器,改密码也只需要改一处。但这一步的门槛和风险都明显更高:

  • 必须预先给每个用户配好 uidNumber/gidNumber/homeDirectory,否则登录会失败。
  • 身份源一旦断了(网络故障、slapd 挂掉),如果本地没缓存,所有人都登不进机器,包括你自己。所以一定要保留一个本地应急账号,绝不把系统的 root 登录完全押在 LDAP 上。
  • PAM 配置写错会导致 SSH 直接无法登录,改动前最好在带外控制台或另开一个 SSH 会话里操作。

对多数个人站长来说,先在 Web 应用层面跑通 LDAP 就够了。等对目录服务足够熟悉、也接受了上面的风险,再考虑把系统登录也接进来。

备份与排错

LDAP 里存的是「谁能进你的所有系统」的关键数据,备份优先级很高。别用 stop 服务再 tar 目录这种粗暴方式,标准做法是在线导出:

slapcat -n 0 > config-backup.ldif      # 备份配置库
slapcat -n 1 > data-backup.ldif        # 备份主数据(MDB 通常是 db 1)
slapadd -n 1 -l data-backup.ldif       # 恢复:先把库清空再导入

排错时最常用的几条命令:

# 匿名查看根 DSE,确认服务与命名上下文
ldapsearch -x -H ldap://localhost -s base -b "" namingContexts

# 用特定用户 bind,验证密码
ldapwhoami -x -H ldap://localhost -D uid=alice,ou=people,dc=example,dc=com -W

# 查看日志(Debian 下 slapd 默认写到 syslog)
journalctl -u slapd -n 100 --no-pager

常见报错对应关系:Invalid credentials (49) 说明密码错或 bind DN 写错;Insufficient access (50) 基本是 ACL 挡住了,多半是 bind 用的账号权限不够;No such object (32) 是 Base DN 写错,目录里没有这棵子树。把这三条记住,八成问题能自己定位。

小结

LDAP 的价值不在技术多新,而在于它把「身份」这块最难管的东西收敛到了一处。装一台 OpenLDAP,用户和组只维护一份;新应用接进来只需要填地址、Base DN、Bind DN、过滤条件四个字段;密码存哈希、连接走 TLS、ACL 锁死密码属性,安全边界就立起来了。对一个人管一堆自建服务的站长来说,这是一次性投入、长期省心的基础设施。

落地顺序建议是:先装 slapd 建好 people/groups 两个 OU,再建一个只读服务账号接第一个 Web 应用验证流程,跑通之后再逐个迁移剩余应用,最后才考虑系统登录这种高风险场景。稳扎稳打,LDAP 会成为你运维体系里最稳的那块地基。

Last modification:October 1st, 2026 at 01:31 pm

Leave a Comment