Self-Service Password 1.8.2 Docker Compose 部署指南
Self-Service Password 1.8.2 与 OpenLDAP 的 Docker Compose 全链路部署实录:镜像选型与版本实查、目录初始化、SSP 对接、自助改密、密码策略校验、邮件重置与安全问题重置实测,附 keyphrase 强制检查、SMTP STARTTLS、ACL 权限三处排障。
Self-Service Password 1.8.2 Docker Compose 部署指南
目标:用 Docker Compose 把 Self-Service Password(下文简称 SSP)1.8.2 与其强依赖的 LDAP 目录(OpenLDAP)一次拉起,完成目录初始化、SSP 对接、自助改密、密码策略校验、安全问题重置与邮件重置链路的全流程验证。本文全部命令与回显均在 Ubuntu 22.04.4 LTS(x86_64)+ Docker 29.8.1 + Docker Compose v5.5.1 环境实测通过,SSP 与 OpenLDAP 版本、页面响应、LDAP 落库结果均为实测回显。
一、环境信息
本文实测环境的关键信息如下,读者复现时请对照自己的版本:
- 操作系统:Ubuntu 22.04.4 LTS(x86_64)
- Docker:29.8.1;Docker Compose:v5.5.1
- SSP:ltbproject/self-service-password:1.8.2(内部基于 PHP 8.5.12 + Apache,实测
php -v回显PHP 8.5.12 (cli) (built: Oct 21 2026 15:49:40) (NTS)) - OpenLDAP:osixia/openldap:latest(实测
slapd -V回显$OpenLDAP: slapd 2.4.57+dfsg-1~bpo10+1,即 slapd 2.4.57) - 邮件测试通道:axllent/mailpit:v1.31.6(实测
/mailpit --version回显/mailpit v1.31.6 compiled with go1.27.2) - 网络规划:独立 bridge 网络
self-service-password-182-net,子网10.89.76.0/24 - 对外端口:SSP Web 服务映射宿主机
8138端口(HTTP) - 目录基准:
dc=example,dc=org,管理员cn=admin,dc=example,dc=org,测试用户alice、bob - 相关背景认知见《Self-Service Password 1.8.2 介绍与选型指南》
二、镜像选型说明
2.1 SSP 官方镜像实查
SSP 官方维护 Docker 镜像 ltbproject/self-service-password,发布在 Docker Hub。本文拉取并实测的 1.8.2 tag 关键元数据如下(来自镜像 label,实测 docker inspect 回显):
| label | 值 |
|---|---|
| org.opencontainers.image.base.name | php:8.5.12-apache-trixie |
| org.opencontainers.image.description | Self Service Password is a simple PHP application that allows users to change their password on an LDAP directory |
| org.opencontainers.image.documentation | https://self-service-password.readthedocs.io/ |
| org.opencontainers.image.licenses | GPL-2+ |
| org.opencontainers.image.source | https://github.com/ltb-project/self-service-password/ |
| org.opencontainers.image.vendor | LTB-project.org |
| org.opencontainers.image.version | 1.8.2 |
容器内应用代码同样自带版本号(实测回显):
docker exec self-service-password-182-web grep -m1 'version' /var/www/htdocs/index.php
$version = "1.8.2";
页面页脚亦显示版本(实测):LDAP Tool Box Self Service Password - version 1.8.2。三处证据互相印证,镜像内容与 1.8.2 版本一致。
PHP 扩展方面实测(php -m):ldap 扩展已内置,sockets 扩展没有内置——这会影响后续自写 SMTP 测试桩的可行性(见 4.3 节排障)。
2.2 OpenLDAP 依赖镜像实查
SSP 本身不带目录服务,必须对接一个 LDAPv3 目录(OpenLDAP、Active Directory、389 DS 等均可,官方文档 Presentation 章节列出兼容清单)。本文选 osixia/openldap 做目录端,原因:社区使用最广、bootstrap LDIF 初始化机制成熟。实测版本:
docker exec self-service-password-182-openldap slapd -V
@(#) $OpenLDAP: slapd 2.4.57+dfsg-1~bpo10+1 (Jan 30 2021 06:59:51) $
⚠️ 注意:
osixia/openldap在镜像加速源上只有latesttag 可拉取,1.5.8等版本化 tag 实测返回 404(MANIFEST_UNKNOWN)。本文实际使用osixia/openldap:latest并以slapd -V实查为准(2.4.57),不臆造版本号。
2.3 非容器渠道(deb/rpm/tar)并陈
不使用容器部署时,SSP 1.8.2 的官方发行渠道(与《Self-Service Password 1.8.2 介绍与选型指南》溯源结论一致,查询日期 2026-10-24):
- Debian/Ubuntu:LTB 官方 apt 仓库(
ltb-project.org提供源配置),apt install self-service-password; - RHEL/CentOS:LTB 官方 rpm 仓库,
yum install self-service-password; - tar.gz 归档:LTB 下载页
ltb-project.org/download.html提供源码/发行包,解压到 Web 根目录配合 PHP-FPM/Apache 部署; - Docker:即本文的
ltbproject/self-service-password。
容器方式的优势是 PHP 运行时、扩展(ldap)、Web 服务器全部预置好,开箱即用;deb/rpm 方式适合已有 Apache + PHP 平台的复用场景。
三、前置条件
- 一台可运行 Docker 的 Linux 主机(本文 Ubuntu 22.04.4),Docker 与 Compose 插件已装好;
- 宿主机目标端口空闲(本文用 8138,
ss -ltn确认未被占用); - 目标网段未被现有网络占用(本文子网
10.89.76.0/24,创建前用docker network inspect复核); - 规划好目录基准 DN 与管理员密码(本文
dc=example,dc=org、管理员密码q1w2e3r4); - 若需要真实邮件投递,准备一个 SMTP 服务器;本文用 mailpit 在同栈内起一个只收不发 SMTP sink,用于验证邮件链路。
四、部署步骤
4.1 Compose 编排文件
部署目录约定为 /opt/self-service-password-1.8.2/,其下 docker-compose.yml 全文如下(实测可直接启动):
name: self-service-password-182-stack
services:
openldap:
image: osixia/openldap:latest
container_name: self-service-password-182-openldap
hostname: openldap
environment:
LDAP_ORGANISATION: "Example Org"
LDAP_DOMAIN: "example.org"
LDAP_BASE_DN: "dc=example,dc=org"
LDAP_ADMIN_PASSWORD: "q1w2e3r4"
LDAP_CONFIG_PASSWORD: "q1w2e3r4"
LDAP_RFC2307_SCHEMA: "false"
volumes:
- ./openldap/ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom:ro
command: ["--copy-service"]
networks:
- sspnet
ssp:
image: ltbproject/self-service-password:1.8.2
container_name: self-service-password-182-web
ports:
- "8138:80"
volumes:
- ./ssp/config.inc.local.php:/var/www/conf/config.inc.local.php:ro
depends_on:
- openldap
networks:
- sspnet
mailsink:
image: axllent/mailpit:v1.31.6
container_name: self-service-password-182-mailsink
hostname: mailsink
environment:
MP_SMTP_AUTH_ACCEPT_ANY: "1"
MP_SMTP_AUTH_ALLOW_INSECURE: "1"
networks:
- sspnet
networks:
sspnet:
name: self-service-password-182-net
driver: bridge
ipam:
config:
- subnet: 10.89.76.0/24
💡 说明:compose 顶层必须显式写
name:(本文self-service-password-182-stack),避免多套部署共用目录前缀时误删其他栈。
4.2 目录初始化 LDIF
./openldap/ldif/ 下放两个 bootstrap LDIF,OpenLDAP 首次启动时自动导入。01-base.ldif(组织单元):
dn: ou=people,dc=example,dc=org
objectClass: organizationalUnit
ou: people
dn: ou=services,dc=example,dc=org
objectClass: organizationalUnit
ou: services
02-users.ldif(测试用户,初始密码均为 q1w2e3r4):
dn: uid=alice,ou=people,dc=example,dc=org
objectClass: inetOrgPerson
uid: alice
cn: Alice
sn: Wang
mail: alice@example.org
mobile: 13800000001
userPassword: q1w2e3r4
dn: uid=bob,ou=people,dc=example,dc=org
objectClass: inetOrgPerson
uid: bob
cn: Bob
sn: Li
mail: bob@example.org
mobile: 13800000002
userPassword: q1w2e3r4
⚠️ 注意:测试用户同时带
另有 06-acl.ldif(ACL 调整,安全问题重置链路必需,否则会报 Insufficient access,见 4.5 与十一.3):
dn: olcDatabase={1}mdb,cn=config
changetype: modify
delete: olcAccess
-
add: olcAccess
olcAccess: to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break
olcAccess: to attrs=userPassword,shadowLastChange by self write by dn="cn=admin,dc=example,dc=org" write by anonymous auth by * none
olcAccess: to attrs=info,objectClass by self write by dn="cn=admin,dc=example,dc=org" write by * none
olcAccess: to * by self read by dn="cn=admin,dc=example,dc=org" write by * none
SSP 的配置覆盖文件 ./ssp/config.inc.local.php 全文见第七节。
4.3 启动栈
docker compose up -d
实测启动回显:
Network self-service-password-182-net Created
Container self-service-password-182-openldap Created
Container self-service-password-182-mailsink Created
Container self-service-password-182-web Created
Container self-service-password-182-mailsink Started
Container self-service-password-182-openldap Started
Container self-service-password-182-web Started
状态确认:
docker compose ps --format '{{.Name}} {{.Status}}'
self-service-password-182-mailsink Up 16 seconds (healthy)
self-service-password-182-web Up About a minute
self-service-password-182-openldap Up About a minute
排障实录(mailsink 首选方案被否):最初计划复用 SSP 镜像跑一个自写 PHP SMTP sink,结果容器退出,docker logs 实测回显:
PHP Fatal error: Uncaught Error: Call to undefined function socket_create() in /work/smtp_sink.php:7
定位:docker exec self-service-password-182-web php -m | grep -i socket 无输出——官方 SSP 镜像未编译 sockets 扩展。解决:改用本地已有 axllent/mailpit:v1.31.6 作为 SMTP sink(收信验证用途完全等价),即上文 compose 中的 mailsink 服务。
4.4 验证:SSP 连接 LDAP 与页面可用性
- 目录初始化结果(在 OpenLDAP 服务器本机执行):
ldapsearch -x -H ldap://localhost -b dc=example,dc=org -D cn=admin,dc=example,dc=org -w q1w2e3r4 '(uid=*)' uid mail
实测回显(节选):
dn: uid=alice,ou=people,dc=example,dc=org
uid: alice
mail: alice@example.org
dn: uid=bob,ou=people,dc=example,dc=org
uid: bob
mail: bob@example.org
# numEntries: 2
- ACL 落库确认(
olcDatabase={1}mdb的olcAccess,实测ldapsearch -b cn=config回显节选):
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break
olcAccess: {1}to attrs=userPassword,shadowLastChange by self write by dn="cn=admin,dc=example,dc=org" write by anonymous auth by * none
olcAccess: {2}to attrs=info,objectClass by self write by dn="cn=admin,dc=example,dc=org" write by * none
olcAccess: {3}to * by self read by dn="cn=admin,dc=example,dc=org" write by * none
- SSP Web 页面(宿主机 8138 端口):
curl -s -o /tmp/ssp_page.html -w 'http=%{http_code} size=%{size_download}\n' http://localhost:8138/
grep -o '<title>[^<]*</title>' /tmp/ssp_page.html
实测回显:
http=200 size=4084
<title>Self service password</title>
至此「SSP → LDAP」依赖链全部打通:目录里有用户、SSP 页面可访问、SSP 配置指向 ldap://openldap:389。
4.5 全链路实测:自助改密 + 策略校验 + 邮件通知
以下操作全部通过 HTTP 接口实测(等价于浏览器操作)。
(1) 弱密码被策略拦截。alice 以旧密码 q1w2e3r4 登录改密表单,提交过弱新密码 abc123:
curl -s -c /tmp/ssp_a.jar -o /dev/null 'http://localhost:8138/index.php?action=change'
curl -s -b /tmp/ssp_a.jar -o /tmp/t1_weak.html \
-d 'login=alice' --data-urlencode 'oldpassword=q1w2e3r4' \
--data-urlencode 'newpassword=abc123' --data-urlencode 'confirmpassword=abc123' \
'http://localhost:8138/index.php?action=change'
页面实测返回(alert 区域):
Your password does not have enough special characters
(2) 合规密码改密成功。提交 N3wPass!2026:
RESULT: Your password was changed
落库验证(SSHA 散列由目录端生成,实测 ldapsearch (uid=alice) userPassword 回显):
userPassword:: e1NTSEF9cGVBNks4bDBHanhKbVVlL21sRmFCcTQyaER6MFUvZFk=
base64 解码首段为 {SSHA}…,确认目录端 SSHA 散列。新旧密码双向验证:
ldapwhoami -x -H ldap://localhost -D uid=alice,ou=people,dc=example,dc=org -w q1w2e3r4
ldap_bind: Invalid credentials (49)
ldapwhoami -x -H ldap://localhost -D uid=alice,ou=people,dc=example,dc=org -w 'N3wPass!2026'
dn:uid=alice,ou=people,dc=example,dc=org
旧密码立即失效、新密码认证通过。
(3) 改密通知邮件。改密后 mailpit 实测收到(mailpit API 回显):
total: 1
TO: alice@example.org | SUBJ: Your password has been changed
⚠️ 注意:首次实测邮件未发出,
docker logs报send_mail: SMTP connect() failed.。根因与修复见 4.6 与十一.2。
4.6 邮件重置与安全问题重置链路实测
(1) 邮件 token 重置全链路。GET index.php?action=sendtoken 后提交 login=alice(需携带页面下发的 formtoken 隐藏域,实测缺失时返回 Missing token):
RESULT: If the account exists, a confirmation email has been sent to the associated email address
mailpit 收到第二封邮件(实测):
SUBJ: Reset your password
邮件正文中的重置链接(实测原文节选):
http://ssp.example.com/?action=resetbytoken&token=3vUCAAkWZC9OyfkK8KEpX1EeY5bUBnGupaPmfRf8NB%2FvkZfP5ZmJaXe7YmpnqBCUvNBI%2BYRTir%2FcA1UYQIZXSL8U4ZfleNVl611WqpGjc5wsRv7e72v6RgZGaqkGvVNFF7Z2SmiMtu0qBxeveQ3Pm6F80ZX8z0%2F6vvJNUFk69joKw6x7G7wT5hLbHtQtu4rVuwPlCg%3D%3D
💡 说明:链接主机名来自
$reset_url配置(默认http://ssp.example.com),生产环境必须改成对外可达的真实地址;token 参数为加密串,与发送时的一致性由 keyphrase 保证。
打开重置链接后表单自带 token 隐藏域与已锁定的用户名,提交新密码 R3s3t!Ok77x:
RESULT: Your password was changed
双向验证(实测):新密码 ldapwhoami 返回 dn:uid=alice,...;旧密码 N3wPass!2026 返回 Invalid credentials (49)。
(2) 安全问题重置全链路。先登录设置答案(index.php?action=setquestions,用当前密码鉴权,问题选 What is your favorite color?,答案 teal):
SET RESULT: Your answer has been registered
落库验证(实测 ldapsearch (uid=alice) info objectClass 节选):
objectClass: inetOrgPerson
objectClass: extensibleObject
info: 3vUCACQAzkDYd9EtvhkWagaFWLMkQ/mpTC2xpJuQ3J6zBve+L2siiUWGfnyPxDYob7EoDUgP
答案以 keyphrase 加密后写入 info 属性,同时条目自动追加 extensibleObject 辅助类(对应 $answer_objectClass 配置)。随后走 index.php?action=resetbyquestions:
- 正确答案
teal+ 新密码Q9Reset!Fine2→ 实测返回Your password was changed,且ldapwhoami -w 'Q9Reset!Fine2'认证通过; - 错误答案 → 实测返回
Your answer is incorrect,密码保持不变。
至此 B 篇要求的四条实测链路(登录改密、策略校验、安全问题重置、邮件通知/邮件重置)全部实测通过。
五、数据持久化说明
本文 compose 的持久化策略与生产建议:
| 数据 | 本文栈 | 生产建议 |
|---|---|---|
| LDAP 目录数据(/var/lib/ldap) | 未挂载(容器内) | 挂载 named volume 或 host path,避免目录数据随实例销毁 |
| LDAP 配置(/etc/ldap/slapd.d) | 未挂载 | 同上;ACL 变更一并持久化 |
| SSP 应用配置 | ./ssp/config.inc.local.php 只读挂载 | 同本文(配置文件本身即持久化) |
| SSP 会话/缓存 | 容器内 /var/lib/php | 单实例可不挂载;多实例需共享存储 |
⚠️ 注意:本文为验证型部署,LDAP 数据未落盘是有意为之(重建即恢复初始测试数据)。生产部署务必给 OpenLDAP 加数据卷,并配合定期
slapcat备份。
六、环境变量表
6.1 OpenLDAP(osixia/openldap)常用环境变量(本文实测所用)
| 变量 | 本文值 | 作用 |
|---|---|---|
| LDAP_ORGANISATION | Example Org | 组织名(o 属性) |
| LDAP_DOMAIN | example.org | 域名,推导基准 DN |
| LDAP_BASE_DN | dc=example,dc=org | 显式指定基准 DN |
| LDAP_ADMIN_PASSWORD | q1w2e3r4 | 根 DN(cn=admin,<base>)密码 |
| LDAP_CONFIG_PASSWORD | q1w2e3r4 | cn=admin,cn=config 配置管理员密码 |
| LDAP_RFC2307_SCHEMA | false | 是否启用 RFC2307bis 辅助 schema |
| command | --copy-service | 启用 bootstrap LDIF 复制执行机制 |
6.2 mailpit(mailsink)环境变量
| 变量 | 值 | 作用 |
|---|---|---|
| MP_SMTP_AUTH_ACCEPT_ANY | 1 | 接受任意 SMTP 认证凭据 |
| MP_SMTP_AUTH_ALLOW_INSECURE | 1 | 允许明文认证(仅测试环境) |
SSP 侧无环境变量,一切运行参数走 config.inc.local.php(见第七节)。
七、配置参数说明
SSP 支持在 /var/www/conf/config.inc.local.php 覆盖默认配置(官方主配置 config.inc.php 不建议直接改,升级会被覆盖)。本文实测用的 config.inc.local.php 全文:
<?php
# LDAP directory
$ldap_url = "ldap://openldap:389";
$ldap_starttls = false;
$ldap_binddn = "cn=admin,dc=example,dc=org";
$ldap_bindpw = "q1w2e3r4";
$ldap_base = "dc=example,dc=org";
$ldap_login_attribute = "uid";
$ldap_fullname_attribute = "cn";
$ldap_filter = "(&(objectClass=inetOrgPerson)(uid={login}))";
# Password handling
$ad_mode = false;
$hash = "SSHA";
$ldap_use_exop_passwd = true;
$pwd_show_policy = "always";
$pwd_show_policy_pos = "above";
# Password policy (lab)
$pwd_min_length = 8;
$pwd_max_length = 64;
$pwd_min_lower = 1;
$pwd_min_upper = 1;
$pwd_min_digit = 1;
$pwd_min_special = 1;
$pwd_special_chars = "^a-zA-Z0-9";
$pwd_no_reuse = true;
$pwd_diff_login = true;
$pwd_complexity = 0;
# Features
$use_questions = true;
$use_tokens = true;
$use_sms = false;
$use_captcha = false;
# Mail
$mail_from = "ssp@example.org";
$mail_from_name = "Self Service Password";
$mail_address_use_ldap = true;
$notify_on_change = true;
$mail_smtp_host = 'mailsink';
$mail_smtp_port = 1025;
$mail_smtp_auth = false;
$mail_smtp_secure = '';
$mail_smtp_autotls = false;
# Tokens
$token_lifetime = "3600";
# 1.8.2 强制要求自定义 keyphrase(出厂值 secret 会拒绝服务)
$keyphrase = "88eb2fee3d7b4d1dbbe99573a8be9972";
参数分组说明(全部实测生效):
| 分组 | 参数 | 说明 |
|---|---|---|
| LDAP 连接 | $ldap_url / $ldap_binddn / $ldap_bindpw / $ldap_base | 目录地址与搜索绑定账号;本文指向同栈 ldap://openldap:389 |
| LDAP 匹配 | $ldap_login_attribute / $ldap_filter | 登录名映射 uid、过滤 inetOrgPerson |
| 密码存储 | $hash=SSHA / $ldap_use_exop_passwd=true | 目录端散列 + LDAP EXOP PASSWD 扩展改密(实测落库 {SSHA} 散列) |
| 密码策略 | $pwd_min_length=8 等 | 长度 8-64、四类字符各至少 1、禁与登录名相同/禁复用;实测弱密码被拦、合规密码通过 |
| 功能开关 | $use_questions / $use_tokens / $use_sms=false | 安全问题与邮件 token 开启;短信通道无网关,显式关闭 |
| 邮件 | $mail_smtp_host=mailsink / $mail_smtp_port=1025 / $mail_smtp_secure='' | SMTP 指向同栈 mailpit;明文投递(排障结论见十一.2) |
| Token | $token_lifetime=3600 / $keyphrase | token 有效期 3600 秒;keyphrase 为加密密钥(必须改) |
💡 技术拓展:SSP 的改密写入口有三种——普通 Modify 操作(先算好散列再 replace userPassword)、LDAP EXOP PASSWD 扩展(RFC 3062,明文提交由服务端散列)、AD 模式(UnicodePwd 特殊编码)。
$ldap_use_exop_passwd=true时优先走 EXOP,OpenLDAP 服务端会按自身策略散列存储,这也是实测落库 SSHA 的原因。三者在密码策略执行点上有差异:EXOP 可触发服务端 ppolicy 校验,普通 Modify 则完全由 SSP 侧策略把关。
八、配置文件挂载
| 容器内路径 | 宿主路径 | 说明 |
|---|---|---|
| /var/www/conf/config.inc.local.php | ./ssp/config.inc.local.php | SSP 配置覆盖文件,只读挂载 |
| /container/service/slapd/assets/config/bootstrap/ldif/custom | ./openldap/ldif | OpenLDAP bootstrap LDIF(含 ACL 调整),配合 --copy-service 首次启动执行 |
修改配置后的生效方式:
docker compose restart ssp
⚠️ 注意:bootstrap LDIF 只在目录数据初始化时执行一次。若目录数据卷已初始化,改动 ACL LDIF 需要
docker compose up -d --force-recreate openldap(未挂数据卷时等价重建)或手工ldapmodify到cn=config。
九、等价 docker run
不用 Compose 时,三个服务等价的 docker run 命令(网络需先建):
docker network create --subnet 10.89.76.0/24 self-service-password-182-net
docker run -d --name self-service-password-182-openldap \
--hostname openldap --network self-service-password-182-net \
-e LDAP_ORGANISATION="Example Org" -e LDAP_DOMAIN="example.org" \
-e LDAP_BASE_DN="dc=example,dc=org" -e LDAP_ADMIN_PASSWORD="q1w2e3r4" \
-e LDAP_CONFIG_PASSWORD="q1w2e3r4" \
-v /opt/self-service-password-1.8.2/openldap/ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom:ro \
osixia/openldap:latest --copy-service
docker run -d --name self-service-password-182-mailsink \
--hostname mailsink --network self-service-password-182-net \
-e MP_SMTP_AUTH_ACCEPT_ANY=1 -e MP_SMTP_AUTH_ALLOW_INSECURE=1 \
axllent/mailpit:v1.31.6
docker run -d --name self-service-password-182-web \
--network self-service-password-182-net -p 8138:80 \
-v /opt/self-service-password-1.8.2/ssp/config.inc.local.php:/var/www/conf/config.inc.local.php:ro \
ltbproject/self-service-password:1.8.2
⚠️ 注意:SSP 容器启动依赖 OpenLDAP 已就绪,手工 run 时先起 openldap 再起 web;Compose 的
depends_on只保证启动顺序,首启重试机制由 SSP 内置(连接失败页面会提示,重试即可)。
十、常用运维命令
# 栈状态
docker compose ps
# 跟踪 SSP 日志(含邮件发送、LDAP 错误的 php:notice 行)
docker compose logs -f ssp
# 重启 SSP(改配置后)
docker compose restart ssp
# 目录数据检查
ldapsearch -x -H ldap://localhost -b dc=example,dc=org -D cn=admin,dc=example,dc=org -w q1w2e3r4 '(uid=*)' uid
# ACL 检查(cn=config)
ldapsearch -x -H ldap://localhost -b cn=config -D cn=admin,cn=config -w q1w2e3r4 '(olcDatabase={1}mdb)' olcAccess
# mailpit 收件箱(API)
docker exec self-service-password-182-mailsink wget -qO- 'http://localhost:8025/api/v1/messages?limit=5'
# 镜像版本核对
docker exec self-service-password-182-web grep -m1 'version' /var/www/htdocs/index.php
# 停止与清理
docker compose down
docker compose down -v # 连同数据卷
十一、常见问题
Q1:页面报 "Token encryption requires a random string in keyphrase setting",改密/重置表单不渲染?
1.8.2 对 $keyphrase 做了强制安全检查:默认值 secret 不被接受,首页直接报上述错误且功能表单不渲染。解决:在 config.inc.local.php 设置自定义随机串(如 openssl rand -hex 16 生成的 88eb2fee3d7b4d1dbbe99573a8be9972),docker compose restart ssp 后恢复。keyphrase 同时用于 token 与安全问题答案的加密,一旦有存量数据不得随意更换。
Q2:改密成功但收不到通知邮件,日志报 "send_mail: SMTP connect() failed."?
官方默认 $mail_smtp_secure = 'tls' 且 $mail_smtp_autotls = true,PHPMailer 会尝试 STARTTLS;对无 TLS 的内网 SMTP(本文 mailpit)握手失败即报连接失败(实测 docker logs 回显:[php:notice] send_mail: SMTP connect() failed.、Error while sending change email to alice@example.org (user alice))。解决:内网明文 SMTP 环境显式 $mail_smtp_secure = ''; 与 $mail_smtp_autotls = false;,重启后实测邮件到达(mailpit total: 1,主题 Your password has been changed)。
Q3:安全问题答案保存失败,日志报 "LDAP - Modify answer (error 50 (Insufficient access)"? 两层原因叠加(均为实测复现):
- SSP 默认以用户自身身份写答案,而 OpenLDAP 默认 ACL 只给 self 写
userPassword的权限,写info被拒(error 50)——需按 4.2 节06-acl.ldif给 self 开放info,objectClass写权; - 即使放开 ACL,还会暴露第二层错误:
info属性不在inetOrgPerson的允许属性表内(实测 alice 身份ldapmodify回显ldap_modify: Object class violation (65) / attribute 'info' not allowed)——SSP 的机制是同时给条目追加extensibleObject辅助类($answer_objectClass = "extensibleObject"),因此 ACL 必须同时覆盖objectClass的写权。两步都完成后实测答案写入成功(info出现加密串、objectClass多出extensibleObject)。
Q4:sendtoken 提交返回 "Missing token"?
1.8.x 的所有表单都带 formtoken 隐藏域(防 CSRF,实测 120 秒有效期,日志回显 generated form token: ... valid for 120 s)。脚本化调用必须先 GET 表单页、提取 formtoken 再随 POST 提交;浏览器正常操作不受影响。
Q5:osixia/openldap 版本化 tag 拉不到?
部分镜像加速源只有 latest 可用(1.5.8 等 tag 返回 404)。建议:拉 latest 后用 slapd -V 实查版本并记录(本文 2.4.57),或改用有完整版本 tag 的 OpenLDAP 镜像源。
Q6:邮件重置链接的主机名是 ssp.example.com?
链接由 $reset_url 生成,默认值即 http://ssp.example.com(实测邮件正文)。生产环境改成对外地址,如 $reset_url = "https://ssp.example.org";。
十二、参考链接
- 官方文档(安装/配置):https://self-service-password.readthedocs.io/en/stable/
- 官方仓库:https://github.com/ltb-project/self-service-password
- LTB 项目官网:https://ltb-project.org
- osixia/openldap 镜像:https://hub.docker.com/r/osixia/openldap
- mailpit:https://github.com/axllent/mailpit
- 本专栏系列:《Self-Service Password 1.8.2 介绍与选型指南》(定位与选型,本篇部署的背景认知)、《Self-Service Password 1.8.2 安全加固指南》(上线前加固清单)
- 同类产品参考:《2FAuth 8.0.3 介绍与选型指南》《Psono v7.5.3 介绍与选型指南》