lldap 0.6.3 配置参数详解

逐项解析 lldap 0.6.3 配置参数:二进制 + systemd 形态的参数查看与修改方式、参数总览与核心参数分类详解(端口/Base DN/key_seed/jwt_secret 等)、用户/组管理实操回显(JWT 携带组鉴权)、生产配置模板与常见问题,全部命令与回显实测照抄。

lldapIAMJWT2026/10/19

lldap 0.6.3 配置参数详解

目标:以真实服务器形态(二进制 + systemd 托管)逐节实测 lldap 0.6.3 主配置文件 /etc/lldap/lldap_config.toml 的全部核心参数,并给出用户/组管理的实操回显。 实测声明:本文全部命令与回显均来自 2026-10-19 的真实环境操作,逐字照抄。环境变量注入形态见《lldap Docker Compose 部署指南》;本文之外的前置安装细节以官方文档为准。

一、环境信息

  • 操作系统:Ubuntu 24.04 LTS(x86_64),主机名 lldap-server
  • 软件版本:lldap 0.6.3(官方预编译二进制 amd64-lldap.tar.gz,sha256 e80b50ef39be61de9f1d75182b1e1fa5d82c672261c7e34fd8ffec5455ed3883)
  • 落位路径:
    • 二进制:/opt/lldap/lldap(附带 /opt/lldap/lldap_set_password、/opt/lldap/lldap_migration_tool、/opt/lldap/app/ Web 资源)
    • 配置:/etc/lldap/lldap_config.toml(属主 root:lldap,权限 640)
    • 数据:/var/lib/lldap/(SQLite 主库 users.db)
    • 服务账号:系统用户 lldap(/usr/sbin/nologin)
  • 服务托管:systemd 单元 lldap.service
  • 监听端口:LDAP 3890、Web/API 17170
  • 示例账号:管理员 admin / q1w2e3r4,普通用户 jsmith / q1w2e3r4(测试环境值)
  • 版本与子命令确认(实测回显):
# /opt/lldap/lldap --version
lldap 0.6.3
# /opt/lldap/lldap --help
lldap is a lightweight LDAP server

Usage: lldap <COMMAND>

Commands:
  export_graphql_schema  Export the GraphQL schema to *.graphql
  run                    Run the LDAP and GraphQL server
  healthcheck            Test whether the LDAP and GraphQL server are responsive
  send_test_email        Send a test email
  create_schema          Create database schema
  help                   Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

二、前置条件

lldap 以专用系统账号运行,先完成账号、目录与配置文件落位(实测回显):

# useradd --system --home-dir /var/lib/lldap --shell /usr/sbin/nologin lldap
# mkdir -p /etc/lldap /var/lib/lldap
# chown -R lldap:lldap /var/lib/lldap

写入本文实测使用的 /etc/lldap/lldap_config.toml(完整内容见第七章生产模板;参数逐项解析见第五章),并收紧权限(内含管理口令与密钥):

# chown root:lldap /etc/lldap/lldap_config.toml && chmod 640 /etc/lldap/lldap_config.toml
# ls -la /etc/lldap/
total 12
drwxr-xr-x 2 root lldap 4096 Oct 19 05:12 .
drwxr-xr-x 1 root root  4096 Oct 19 05:12 ..
-rw-r----- 1 root lldap 1146 Oct 19 05:12 lldap_config.toml

systemd 单元 /etc/systemd/system/lldap.service:

[Unit]
Description=lldap - Light LDAP implementation for authentication
Documentation=https://github.com/lldap/lldap
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/opt/lldap
User=lldap
Group=lldap
ExecStart=/opt/lldap/lldap run --config-file /etc/lldap/lldap_config.toml
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

WorkingDirectory 不可省:Web 资源默认按工作目录下的 ./app 查找,缺失时服务能启动但 Web UI 资源加载失败,日志出现 Cannot find ./app, please ensure that assets_path is set correctly(完整排障过程见《lldap 日常运维手册》8.2 节)。

启用并启动(实测回显):

# systemctl daemon-reload
# systemctl enable --now lldap
Synchronizing state of lldap.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing /usr/lib/systemd/systemd-sysv-install enable lldap
Created symlink /etc/systemd/system/multi-user.target.wants/lldap.service → /usr/lib/systemd/system/lldap.service.
# systemctl is-active lldap
active

三、参数查看与修改方式

lldap 参数有三个来源,优先级从高到低:环境变量(LLDAP_ 前缀)> 配置文件 > 内置默认值。

方式适用场景示例
配置文件 /etc/lldap/lldap_config.toml常规生产形态,参数集中可审计ldap_port = 3890
命令行参数临时覆盖/测试,粒度最细lldap run --ldap-port 3891
环境变量(LLDAP_*)密钥类注入、编排系统交付LLDAP_JWT_SECRET=...

运行时查看实际配置的三个途径:

  1. 启动日志回显:服务启动时以 DEBUG/INFO 打印生效配置摘要(verbose = true 时输出全量配置,含 Config file path: ... 与各段取值);
  2. /etc/lldap/lldap_config.toml 直接读取(属主 root:lldap,普通用户无权读);
  3. 默认模板对照:官方配置模板内置在发布产物中,逐项注释完备(第七章模板即以此为基础)。

修改后的生效方式统一为重启服务:

# systemctl restart lldap

配置错误会在启动日志中直接暴露(如 5.3 节 jwt_secret 缺失即拒绝启动),不会"带病运行"。

四、参数总览

lldap_config.toml 顶层参数速查(v0.6.3,默认值来自官方模板):

参数类型/默认值作用
verbosefalseDEBUG 级日志开关
ldap_host"0.0.0.0"LDAP 监听地址
ldap_port3890LDAP 监听端口
http_host"0.0.0.0"HTTP(Web/API) 监听地址
http_port17170HTTP(Web/API) 监听端口
http_url"http://localhost"对外访问 URL,用于密码重置邮件中的链接
jwt_secret无默认(必填)JWT 签名密钥
ldap_base_dn"dc=example,dc=com"目录树根
ldap_user_dn"admin"管理员用户 ID
ldap_user_email"admin@localhost"管理员邮箱
ldap_user_pass无默认(必填)初始管理员口令
database_url"sqlite://users.db?mode=rwc"数据库连接串
key_seed / key_file"RanD0m STR1ng" / private_key服务端密钥(种子派生或文件加载,二选一)
force_ldap_user_pass_resetfalse用配置值强制重置管理员口令(一次性)
force_update_private_keyfalse服务端密钥更换后强制刷新
[smtp_options]全部注释密码找回邮件(见 5.6)
[ldaps_options]enabled=falseLDAPS 监听(见 5.7)
[healthcheck_options]localhost内置健康检查目标地址(见 5.7)

五、核心参数分类详解

5.1 服务监听参数(ldap_host/ldap_port/http_host/http_port/http_url)

实测环境取值:

ldap_host = "0.0.0.0"
ldap_port = 3890
http_host = "0.0.0.0"
http_port = 17170
http_url = "http://192.168.146.128:17170"

启动后实测监听状态:

# ss -ltn | grep -E ':3890|:17170'
LISTEN 0      2048         0.0.0.0:3890       0.0.0.0:*
LISTEN 0      2048         0.0.0.0:17170      0.0.0.0:*

要点:

  • ldap_port 改动后所有下游应用(《Authelia 4.39 介绍与选型指南》《Authentik 介绍与选型指南》、Nextcloud 等)需同步修改 LDAP 地址;非特权端口需注意 lldap 以 lldap 用户运行,绑 389/636 特权端口要给 systemd 单元加 AmbientCapabilities=CAP_NET_BIND_SERVICE;
  • http_url 是密码重置邮件链接的地址来源,必须填用户浏览器可达的地址。实测将其设为 http://192.168.146.128:17170 后,找回邮件中的重置链接形如 http://192.168.146.128:17170/reset-password/step2/<token>(完整邮件见 5.6);
  • HTTP 端口不提供原生 HTTPS(官方架构文档明确),TLS 由前置反向代理承担;LDAPS 由 [ldaps_options] 单独开启。

5.2 base_dn 与目录结构(ldap_base_dn)

ldap_base_dn = "dc=example,dc=com"

lldap 的目录树结构是固定的:base_dn 之下,用户固定挂在 OU=people,组固定挂在 OU=groups(从条目 DN 路径即可验证;base_dn 本身与两个 OU 不作为独立条目返回)。实测全量导出(6 条目:2 用户 + 4 组):

# ldapsearch -x -H ldap://127.0.0.1:3890 -D "cn=admin,ou=people,dc=example,dc=com" -w q1w2e3r4 -b "dc=example,dc=com" "(objectClass=*)"
# extended LDIF
#
# LDAPv3
# base <dc=example,dc=com> with scope subtree
# filter: (objectClass=*)
# requesting: ALL
#

# admin, people, example.com
dn: uid=admin,ou=people,dc=example,dc=com
cn: Administrator
createtimestamp: 20261019051213Z
entryuuid: de0245fe-3501-3e99-9f6e-9a066308e948
mail: admin@example.com
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: mailAccount
objectclass: person
uid: admin

# jsmith, people, example.com
dn: uid=jsmith,ou=people,dc=example,dc=com
cn: John Smith
createtimestamp: 20261019051514Z
entryuuid: 86d14826-2fcb-347a-a0aa-7095d40e98e5
mail: jsmith@example.com
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: mailAccount
objectclass: person
uid: jsmith

# developers, groups, example.com
dn: cn=developers,ou=groups,dc=example,dc=com
cn: developers
entryuuid: 0944c08f-e42e-3dec-a7be-5b43ce970dd8
member: uid=jsmith,ou=people,dc=example,dc=com
objectclass: groupOfUniqueNames
objectclass: groupOfNames
uid: developers
uniquemember: uid=jsmith,ou=people,dc=example,dc=com

# lldap_admin, groups, example.com
dn: cn=lldap_admin,ou=groups,dc=example,dc=com
cn: lldap_admin
entryuuid: 13a64509-a5c6-3b4b-8343-9a1a09566633
member: uid=admin,ou=people,dc=example,dc=com
objectclass: groupOfUniqueNames
objectclass: groupOfNames
uid: lldap_admin
uniquemember: uid=admin,ou=people,dc=example,dc=com

# lldap_password_manager, groups, example.com
dn: cn=lldap_password_manager,ou=groups,dc=example,dc=com
cn: lldap_password_manager
entryuuid: c46b4c82-bf4d-3549-ad19-7869999df0ec
objectclass: groupOfUniqueNames
objectclass: groupOfNames
uid: lldap_password_manager

# lldap_strict_readonly, groups, example.com
dn: cn=lldap_strict_readonly,ou=groups,dc=example,dc=com
cn: lldap_strict_readonly
entryuuid: 6da91b3b-eae7-3280-b4f3-79b23c788d43
objectclass: groupOfUniqueNames
objectclass: groupOfNames
uid: lldap_strict_readonly

# search result
search: 2
result: 0 Success
control: 1.2.840.113556.1.4.319 false MAUCAQYEAA==
pagedresults: estimate=6 cookie=

# numResponses: 7
# numEntries: 6

要点:

  • 初始化自动创建 lldap_admin(全部管理权限)、lldap_password_manager(仅密码管理)、lldap_strict_readonly(只读)三个内置组,权限模型即围绕这三个组展开;
  • 用户条目 objectClass 为 person / inetOrgPerson / posixAccount / mailAccount,组条目为 groupOfNames / groupOfUniqueNames 且同时携带 member 与 uniquemember 成员属性(下游应用的过滤器按此书写);
  • 下游应用(如《Authelia 4.39 介绍与选型指南》)的用户过滤器/组过滤器要按 ou=people/ou=groups 书写;
  • base_dn 建库后不宜更改(所有 DN 均随之失效),规划时直接使用组织域名。

5.3 认证与会话参数(jwt_secret)

jwt_secret = "18e5f12c62f8b6754584cb1a3005bdd98149f5672f81ef63fd0661379aee9403"

生成方式(实测):

# openssl rand -hex 32
18e5f12c62f8b6754584cb1a3005bdd98149f5672f81ef63fd0661379aee9403

缺失即拒绝启动(v0.6.2 起行为)。实测将配置中 jwt_secret 注释后启动:

# /opt/lldap/lldap run --config-file /tmp/test_nojwt.toml
2026-10-19T05:36:16.199118087+00:00  INFO     ┝━ i [info]: Starting LLDAP version 0.6.3
2026-10-19T05:36:16.199831860+00:00  INFO     ┝━ i [info]: Starting the LDAP server on port 13890
2026-10-19T05:36:16.199848466+00:00  INFO     ┕━ i [info]: Starting the API/web server on port 17171
2026-10-19T05:36:16.220421028+00:00  ERROR    ┝━ e [error]: Configuration error: A secret is missing. Please reserve a JWT secret by setting the `JWT_SECRET` environment variable or the `jwt_secret` value in the config file.

要点:

  • jwt_secret 是 Web/API 会话(JWT,HS512)的签名密钥,变更即全量会话失效,所有已登录客户端需重新认证;
  • 建议以 openssl rand -hex 32 一次性生成并纳入备份清单;
  • ldap_user_dn/ldap_user_pass/ldap_user_email 三项仅在首次建库时创建管理员;库已存在后修改 ldap_user_pass 不会改口令,需用 force_ldap_user_pass_reset = true(一次性强制重置,官方提供的唯一"忘记 admin 口令"配置级途径)或 6.3 节 CLI 工具。
<details> <summary><b>技术拓展:jwt_secret 与 key_seed 是两把不同的钥匙</b></summary>

jwt_secret 只负责 JWT 签名(无状态会话防伪造),泄露后果是"可伪造登录态",但不动口令数据。key_seed(或 key_file)派生的是 OPAQUE 服务端密钥,用于加密库内每个用户的口令 blob,泄露/丢失的后果见 5.4——两者必须分开保管,不能同值。

</details>

5.4 密码存储与哈希(key_seed / key_file 与 OPAQUE)

key_seed = "gg93PDzoFn2qyMk8dvg7y2Xk"

lldap 的口令验证走 OPAQUE 协议(RFC 9380)零知识证明,OPAQUE 服务端记录经 Argon2 哈希后,再用 key_seed 派生的服务端私钥加密落库。实测库内形态(SQLite users 表 password_hash 列为加密 blob):

# sqlite3 /var/lib/lldap/users.db ".tables"
group_attribute_schema   memberships              user_attribute_schema
group_attributes         user_attributes          users
group_object_classes     user_object_classes
groups                   jwt_refresh_storage
jwt_storage              metadata
# sqlite3 /var/lib/lldap/users.db "SELECT user_id, length(password_hash), hex(substr(password_hash,1,24)) FROM users;"
admin|192|74A57054742B5384B625BDB82446EA139B464D349F3276A4
jsmith|192|8AE87296E54C0DFB7BE47054E52BAFE33F24D5B1D43B62A9

要点:

  • 每个用户口令存储为 192 字节加密 blob,整库拖走也拿不到可直接比对的传统哈希;官方架构文档指出即便密钥泄露,攻击者仍需逐用户暴力验证;
  • key_seed(或 key_file 指向的 private_key 文件)一旦变更,所有已存口令立即失效(blob 无法解密,登录报"密码验证错误"),因此该值必须一次生成、永久不变、纳入备份;
  • 默认种子 RanD0m STR1ng 是公开值,生产必须替换;
  • 口令重置走官方 CLI 工具(实测见 6.3);
  • 密码哈希策略(Argon2 参数)当前版本未开放为配置项,由内置默认值决定。

5.5 数据库参数(database_url)

database_url = "sqlite:///var/lib/lldap/users.db?mode=rwc"
后端database_url 形态适用
SQLite(默认)sqlite:///var/lib/lldap/users.db?mode=rwc单机、家庭实验室、百人内团队(实测当前库 136 KB/3 用户)
MySQL/MariaDBmysql://lldap:密码@192.168.1.10/lldap多实例汇聚、异地备份池、已有 DBA 体系
PostgreSQLpostgres://lldap:密码@192.168.1.10/lldap同上

要点:

  • SQLite 的备份优势是"拷一个文件",但在线热备请用 sqlite3 .backup(见《lldap 日常运维手册》第四章实测);
  • 从 SQLite 迁到 MySQL/PostgreSQL 有官方迁移工具 lldap_migration_tool(随二进制包附带);
  • ?mode=rwc 表示库文件不存在时自动创建,首次启动同时完成 schema 初始化(启动日志 Upgrading DB schema from version 1 ... to version 11)。
<details> <summary><b>技术拓展:为什么 lldap 敢把用户库放在关系数据库</b></summary>

lldap 是"数据库之上的 LDAP 接口层":目录树(ou=people/ou=groups)只是固定视图,真实存储即普通关系表。好处是备份/迁移/监控全部沿用数据库生态,代价是放弃了传统目录服务的高并发读优化与任意 schema 能力——这正是其"轻量统一认证"定位的一部分。

</details>

5.6 SMTP 可选项([smtp_options])

[smtp_options]
#enable_password_reset=true
#server="smtp.example.com"
#port=587
#smtp_encryption = "STARTTLS"
#user="sender@example.com"
#password="password"
#from="LLDAP Admin <sender@example.com>"
#reply_to="Do not reply <noreply@example.com>"
参数说明实测结论
enable_password_reset开启自助找回密码(Web UI 登录页出现"忘记密码")开启后 /auth/reset/step1/<用户> 才可用
server/portSMTP 服务器与端口实测指向 127.0.0.1:2525 本地收件器
smtp_encryptionNONE / TLS / STARTTLS 三选一NONE 模式下配置了 user/password 会发送失败(见下)
user/password发信认证凭据NONE + 凭据 → 服务端拒绝明文认证
from/reply_to发件人与回复地址原样出现在邮件头

失败路径实测(enable_password_reset=true、server=127.0.0.1、port=2525、encryption=NONE 且带凭据,触发找回邮件):

# curl -s -w "\nHTTP:%{http_code}\n" -X POST "http://127.0.0.1:17170/auth/reset/step1/jsmith@example.com"
Internal server error: `Could not send email: error response to `AUTH` command: 535 5.7.8 Error: authentication failed: No compatible authentication mechanism was found (os error 335)`
HTTP:500

将 user/password 注释改为匿名中继后成功路径实测(本地 SMTP 收件器收到完整邮件):

# curl -s -w "HTTP:%{http_code}\n" -X POST "http://127.0.0.1:17170/auth/reset/step1/jsmith@example.com"
HTTP:200
==== 收件器回显 ====
From: LLDAP Admin <noreply@example.com>
To: jsmith@example.com
Subject: [LLDAP] Password reset requested

Hello John Smith,

This message is a reply to a password reset request made from the LLDAP admin interface.

To change your password, please visit http://192.168.146.128:17170/reset-password/step2/h9SWZwdPcA...

服务端日志同步记录发信行为(verbose=true 下):

DEBUG ┝━ i [info]: Sending email to 'jsmith@example.com' as 'LLDAP Admin <noreply@example.com>' via 'noreply@example.com'@'127.0.0.1':'2525'
INFO  ┕━ i [info]: Password reset email sent

要点:

  • http_url 直接决定邮件内链接,改域名/端口后必须同步修改;
  • 生产 SMTP 建议 STARTTLS + 真实凭据;本地中继场景用 NONE 且不配 user/password;
  • SMTP 未开启时,管理员仍可在 Web UI/GraphQL 中为用户设置新口令(走 6.3 CLI 同源接口),找回邮件只是自助通道。

5.7 其他参数(verbose / [ldaps_options] / [healthcheck_options])

  • verbose = true:DEBUG 级日志,输出完整配置回显与 LDAP bind 明细(本文多次实测引用),排障后建议关闭以减小日志量;
  • [ldaps_options]:enabled=true 后在 ldaps_port(默认 6360)监听 TLS 加密的 LDAP,证书用 cert_file/key_file 指定;需要原生加密通道时开启(下游应用配置 ldaps:// 地址);
  • [healthcheck_options]:内置 lldap healthcheck 子命令探测的目标地址(默认 localhost),便于本机自检:
# /opt/lldap/lldap healthcheck --config-file /etc/lldap/lldap_config.toml
2026-10-19T05:35:19.231040080+00:00  INFO     ┝━ i [info]: Testing LDAP
2026-10-19T05:35:19.231691204+00:00  INFO     ┕━ i [info]: Testing GraphQL

(exit code 0 即 LDAP 与 GraphQL 均响应正常。)

六、用户/组管理实操回显

lldap 的 LDAP 接口只读(查询/绑定),全部写操作走 Web UI(管理员图形界面)或 GraphQL API(/api/graphql,认证方式为 Authorization: Bearer <JWT>);另有官方 CLI 工具 lldap_set_password 处理口令。本章以 GraphQL + LDAP 只读验证的方式复现实操全流程。

6.1 获取管理员令牌并建组

# TOKEN=$(curl -s -X POST http://127.0.0.1:17170/auth/simple/login -H "Content-Type: application/json" -d '{"username":"admin","password":"q1w2e3r4"}' | python3 -c "import json,sys;print(json.load(sys.stdin)['token'])")
# curl -s -X POST http://127.0.0.1:17170/api/graphql -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"query":"mutation { createGroup(name: \"developers\") { id displayName } }"}'
{"data":{"createGroup":{"id":4,"displayName":"developers"}}}

6.2 建用户(createUser)

# curl -s -X POST http://127.0.0.1:17170/api/graphql -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"query":"mutation { createUser(user: {id: \"jsmith\", email: \"jsmith@example.com\", displayName: \"John Smith\"}) { id email displayName } }"}'
{"data":{"createUser":{"id":"jsmith","email":"jsmith@example.com","displayName":"John Smith"}}}

6.3 加组成员(addUserToGroup)与口令设置

入参形态错误时报错自带正确签名提示(实测原样保留,便于对照):

# curl -s -X POST http://127.0.0.1:17170/api/graphql -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"query":"mutation { addUserToGroup(user: \"jsmith\", group: 4) { ok } }"}'
{"errors":[{"message":"Expected value of type \"String!\", found 4.","locations":[{"line":1,"column":36}]}]}
{"errors":[{"message":"Unknown argument \"group\" on field \"Mutation.addUserToGroup\".","locations":[{"line":1,"column":45}]}],"data":null}

按提示改用 userId/groupId(成功回显):

# curl -s -X POST http://127.0.0.1:17170/api/graphql -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"query":"mutation { addUserToGroup(userId: \"jsmith\", groupId: 4) { ok } }"}'
{"data":{"addUserToGroup":{"ok":true}}}

口令设置不在 GraphQL(实测 UpdateUserInput 无密码字段、独立 setPassword mutation 不存在,报错回显略),官方路径是 CLI 工具 lldap_set_password:

# /opt/lldap/lldap_set_password --help 2>&1 | head -14
CLI tool to reset the password of a user in the LDAP server

Usage: lldap_set_password [OPTIONS]

Options:
      --base-url <BASE_URL>
          Base url of the LDAP server [default: http://localhost:17170]
      --admin-username <ADMIN_USERNAME>
          Admin username
      --admin-password <ADMIN_PASSWORD>
          Admin password
      --username <USERNAME>
          User name of the user to update
      --password <PASSWORD>
          New password
      --token <TOKEN>
          Admin API token
  -h, --help  Print help
  -V, --version  Print version
# /opt/lldap/lldap_set_password --base-url http://127.0.0.1:17170 --admin-username admin --admin-password q1w2e3r4 --username jsmith --password q1w2e3r4

(静默返回 0 即成功。)

6.4 结果验证(LDAP 只读查询 + 登录)

组成员关系通过 memberOf 属性回读:

# ldapsearch -x -H ldap://127.0.0.1:3890 -D "cn=admin,ou=people,dc=example,dc=com" -w q1w2e3r4 -b "dc=example,dc=com" "(uid=jsmith)" memberOf dn
# extended LDIF
#
# LDAPv3
# base <dc=example,dc=com> with scope subtree
# filter: (uid=jsmith)
# requesting: memberOf dn
#

# jsmith, people, example.com
dn: uid=jsmith,ou=people,dc=example,dc=com
memberOf: cn=developers,ou=groups,dc=example,dc=com

# search result
search: 2
result: 0 Success
control: 1.2.840.113556.1.4.319 false MAUCAQEEAA==
pagedresults: estimate=1 cookie=

# numResponses: 2
# numEntries: 1

普通用户 LDAP 绑定与 Web 登录:

# ldapwhoami -x -H ldap://127.0.0.1:3890 -D "uid=jsmith,ou=people,dc=example,dc=com" -w q1w2e3r4
Result: Success (0)
Additional info: dn:uid=jsmith,ou=people,dc=example,dc=com
# curl -s -X POST http://127.0.0.1:17170/auth/simple/login -H "Content-Type: application/json" -d '{"username":"jsmith","password":"q1w2e3r4"}' | head -c 230
{"token":"eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE3OTIzODMwMzcsImlhdCI6MTc5MjM3OTQzN30...
"refreshToken":"...","status":"Success"}

用户登录 JWT 的 payload 中直接携带 "groups":["developers"]——下游应用无需回查即可完成组鉴权。

查询单用户详情(含组)与全量组列表:

# curl -s -X POST http://127.0.0.1:17170/api/graphql -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"query":"{ user(userId: \"jsmith\") { id email displayName groups { displayName } } }"}'
{"data":{"user":{"id":"jsmith","email":"jsmith@example.com","displayName":"John Smith","groups":[{"displayName":"developers"}]}}}
# curl -s -X POST http://127.0.0.1:17170/api/graphql -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"query":"{ groups { id displayName } }"}'
{"data":{"groups":[{"id":1,"displayName":"lldap_admin"},{"id":2,"displayName":"lldap_password_manager"},{"id":3,"displayName":"lldap_str_readonly"},{"id":4,"displayName":"developers"}]}}

完整的 GraphQL schema 可用内置子命令导出后离线检索:

# /opt/lldap/lldap export_graphql_schema --output /tmp/schema.graphql

七、生产配置模板

实测环境的完整 /etc/lldap/lldap_config.toml(逐值可复现;资源画像:内存常态 RSS ≈16 MiB(启动态 ≈2.3 MiB)、磁盘数据目录 ≈140 KB/3 用户,适合 1C512M 起的任意节点,单实例内网场景):

# /etc/lldap/lldap_config.toml - lldap 主配置(实测环境)
verbose = false
ldap_host = "0.0.0.0"
ldap_port = 3890
http_host = "0.0.0.0"
http_port = 17170
http_url = "http://192.168.146.128:17170"
jwt_secret = "18e5f12c62f8b6754584cb1a3005bdd98149f5672f81ef63fd0661379aee9403"
ldap_base_dn = "dc=example,dc=com"
ldap_user_dn = "admin"
ldap_user_email = "admin@example.com"
ldap_user_pass = "q1w2e3r4"
database_url = "sqlite:///var/lib/lldap/users.db?mode=rwc"
key_seed = "gg93PDzoFn2qyMk8dvg7y2Xk"

[smtp_options]
#enable_password_reset=true
#server="smtp.example.com"
#port=587
#smtp_encryption = "STARTTLS"
#user="sender@example.com"
#password="password"
#from="LLDAP Admin <sender@example.com>"
#reply_to="Do not reply <noreply@example.com>"

[ldaps_options]
#enabled=true
#port=6360
#cert_file="/var/lib/lldap/cert.pem"
#key_file="/var/lib/lldap/key.pem"

[healthcheck_options]
#http_host = "localhost"
#ldap_host = "localhost"

生产替换清单:jwt_secret/key_seed 重新生成、ldap_user_pass 强口令、http_url 改为对外域名、SMTP 全段按 5.6 启用、base_dn 改为组织域名、可选 LDAPS。配置文件属主 root:lldap、权限 640(内含明文口令与密钥)。

八、常见问题

Q1:改了 ldap_user_pass 后 admin 口令没变? 该参数仅首次建库生效。库已存在时改配置不生效,用 force_ldap_user_pass_reset = true 重启一次(一次性),或用 6.3 节 lldap_set_password。

Q2:启动报 A secret is missing ... jwt_secret? jwt_secret 为必填(v0.6.2+ 拒绝启动)。生成:openssl rand -hex 32,填入配置或环境变量 LLDAP_JWT_SECRET(实测报错全文见 5.3)。

Q3:找回密码邮件发不出? 三步排查:① [smtp_options] 是否 enable_password_reset=true;② smtp_encryption 与凭据组合(NONE 不能带 user/password,实测 535 报错见 5.6);③ 服务端日志 Could not send email: 后的具体 SMTP 错误。

Q4:下游应用按 uid 还是 cn 绑定用户 DN? 用户 DN 固定为 uid=<用户ID>,ou=people,<base_dn>(如 uid=jsmith,ou=people,dc=example,dc=com);组 DN 为 cn=<组名>,ou=groups,<base_dn>。管理员绑定 DN 同理是 cn=admin,ou=people,dc=example,dc=com(注意管理员是 uid 属性值 admin,DN 头部用 cn)。

Q5:能否通过 LDAP 直接创建用户? 不能。LDAP 接口为只读子集(官方架构文档),写操作一律走 Web UI / GraphQL API / CLI。需要批量导入时用 GraphQL 批量 mutation 或先在 Web UI 手工建账号。

Q6:key_seed 能换吗? 能换但不可逆:换后所有口令 blob 无法解密,全量用户需重新设密。官方提供 force_update_private_key 配合一次性迁移,操作前必须全量备份(见《lldap 日常运维手册》)。

九、参考链接

  1. 官方配置文档:https://github.com/lldap/lldap/blob/main/docs/config.md
  2. 官方架构文档(OPAQUE/存储设计):https://github.com/lldap/lldap/blob/main/docs/architecture.md
  3. 官方 scripting 文档(GraphQL API 示例):https://github.com/lldap/lldap/blob/main/docs/scripting.md
  4. lldap v0.6.3 Release:https://github.com/lldap/lldap/releases/tag/v0.6.3
  5. OPAQUE 协议(RFC 9380):https://datatracker.ietf.org/doc/rfc9380/
  6. 本系列文档:《lldap 0.6.3 介绍与选型指南》《lldap Docker Compose 部署指南》《lldap 日常运维手册》