5.4 KiB
AD 第一因素接入
/signin 接收 AD 用户名(sAMAccountName)或本域 UPN,使用 Spring Security
ActiveDirectoryLdapAuthenticationProvider 以用户身份执行 LDAPS bind 与目录查询。
不使用额外目录服务账号,不写入 AD,不复制 Authelia 的绑定密码。
成功后重定向到 /signin/mfa,显示目录账号、objectGUID、邮箱、直接所属组及组 DN。
这是密码因素验收页面,MFA 尚未接入,不是完整登录成功。 不创建 Spring Security
认证上下文,不调用 Hydra,不替换现役 Go/Authelia/Gitea 登录链路。演示入口 /preview
仍完全隔离,默认关闭,演示码不能推进真实认证。
目录和组语义
- AD 是身份和组权威。objectGUID 按 AD 混合字节序解析为标准 UUID,作为目录稳定键;
不是已确定的 Hydra
sub。切换前仍须处理现役 issuer/sub 哈希的主体连续性。 - 从
memberOf读取直接成员关系,保留原始 DN;CN 保留大小写,不添加ROLE_, 不把 Spring 的FACTOR_PASSWORD当作组。结果排序,不做应用专用组改写。 - 此轮不展开嵌套组、不推导 primaryGroupID,也不宣称与 Authelia 的有效组集合完全一致。 遇到 ranged memberOf 或不同 DN 同名 CN 拒绝映射,不静默丢组或合并不同主体。
- AD bind 执行密码及账号状态检查;Spring 解析禁用、锁定、密码过期等 AD 子码。 搜索排除 computer 对象,只接受唯一用户条目和合法 objectGUID/sAMAccountName。
- 邮箱作为目录属性展示,不声称
email_verified=true。
启用
正常 JVM 构建:
IAM_DOCKER_USE_SUDO=1 scripts/gradle-in-docker test bootJar
Gradle 与 npm 缓存默认持久挂载,可用 IAM_GRADLE_CACHE、IAM_NPM_CACHE 指定目录。
日常迭代不必每轮跑 Native;新增 AD/JNDI/TLS 路径的原生验收留到阶段性验证。
运行时配置(非秘密):
iam:
ad:
enabled: true
url: ldaps://dc1.ad.ddupan.top:636
domain: ad.ddupan.top
base-dn: DC=ad,DC=ddupan,DC=top
server:
port: 18082
ssl:
enabled: true
certificate: file:/run/iam/browser.crt
certificate-private-key: file:/run/iam/browser.key
servlet:
session:
cookie:
secure: true
same-site: lax
证书与私钥使用外部受管文件,不加入仓库。开发验收可使用本机的 Tailscale HTTPS 域名 及相应证书;用匹配证书的主机名打开,不能用 LAN IP 替代域名后跳过警告。 部署与证书自动续期归 homelab-infra,此轮本地运行不是生产部署。
LDAPS 使用默认 JSSE 信任库并执行服务端域名校验。Samba 当前证书由 OpenBao 内部 CA 签发,需把公开 CA 加入运行用 truststore,并通过 JVM 参数指定:
java -Djavax.net.ssl.trustStore=/run/iam/truststore \
-Djavax.net.ssl.trustStorePassword=changeit -jar iam-login.jar
这里 truststore 仅含公开信任锚,口令不是目录密码。保留所需公共根证书;不得禁用 LDAP endpoint identification 或用信任所有证书的 socket factory。连接/读取超时为 3/5 秒。
浏览器入口只接受 HTTPS;明文请求返回 426。默认不信任转发头。若以后由代理终结 TLS, 必须配合仅受信代理可达的后端网络和转发头配置,不能公开一个信任任意 forwarded header 的 HTTP 端口。当前开发验收由应用直接终结 TLS。
状态与操作
原生表单 POST 带 Spring CSRF token;服务端决定下一页。密码成功后轮换 session ID, 只保存目录身份快照和十分钟期限,不保存密码;重启或“退出并重新验证”清除事务。 页面禁止缓存,内联 JSON 转义 HTML 结束标记。失败页面不披露目录内部异常。
当前每个 session 只保留一份事务,多标签页会共享状态。两秒提交间隔仅用于同事务的 重复提交,不是账号/IP 限流;此 PoC 仅供受控 LAN/Tailscale 验收,生产发布前仍需完善 入口限流、审计、MFA、Hydra 事务和恢复策略。
基础存活检查使用 /actuator/health/liveness。Boot 自动配置的 LDAP 健康项并未连接这里
独立配置的 AD provider,不能把该项当作此认证路径的可用性验证。
本轮验证边界
隔离测试使用真实 TLS、LDAP bind 和搜索,校验正确/错误密码、未知账号、AD 账号状态 子码、GUID 字节序、组名、错误 TLS 主机名、CSRF、HTTPS、会话轮换、超时和密码成功后 仍未完整认证。UnboundID 的 UPN bind 与 AD 子码由测试拦截器模拟,不能替代 Samba AD。 测试证书、私钥与账号全为虚构夹具,不用于实际部署。
2026-09-25:JVM 测试共 12 项通过(原有 6 项、新增 AD 6 项),bootJar 构建通过。
实际 JVM 使用受信 CA 完成 Samba AD RootDSE 查询,HTTPS 页面返回 200。
浏览器已检查登录表单渲染、真实 CSRF 原生 POST 和失败后清空密码;只使用在访问 AD 前
即拒绝的合成外域用户名,不尝试猜测人类密码。浏览器回归共 1 项通过,包含移动端布局。
复现:IAM_AD_URL=https://验收域名:端口 npm --prefix frontend run test:browser -- ad-login.spec.ts。
实际人类密码与组结果仍需维护者
在 HTTPS 页面输入凭据验收,不在聊天、命令行或日志中传递人类密码。
新增 AD 路径尚未进行 Native 测试,不能复用旧 UI 原型的 Native 结论。