接入 AD 密码验证与直接所属组,保留待 MFA 边界

This commit is contained in:
2026-09-25 21:06:39 +00:00
parent 2840f58397
commit d14151928d
18 changed files with 699 additions and 33 deletions
+102
View File
@@ -0,0 +1,102 @@
# 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 构建:
```sh
IAM_DOCKER_USE_SUDO=1 scripts/gradle-in-docker test bootJar
```
Gradle 与 npm 缓存默认持久挂载,可用 `IAM_GRADLE_CACHE`、`IAM_NPM_CACHE` 指定目录。
日常迭代不必每轮跑 Native;新增 AD/JNDI/TLS 路径的原生验收留到阶段性验证。
运行时配置(非秘密):
```yaml
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 参数指定:
```sh
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 前
即拒绝的合成外域用户名,不尝试猜测人类密码。
实际人类密码与组结果仍需维护者
在 HTTPS 页面输入凭据验收,不在聊天、命令行或日志中传递人类密码。
新增 AD 路径尚未进行 Native 测试,不能复用旧 UI 原型的 Native 结论。
+3
View File
@@ -20,6 +20,9 @@ WebAuthn 集成;TOTP、恢复方式与已有 Authelia MFA 的迁移方式需
- Hydra admin 保持内部访问,业务日志不得包含密码、MFA secret、token 或 challenge。
- 原生二进制与隔离 Hydra 完成授权码链路,再验证现有 Gitea 账号及权限。
日常迭代以 JVM 测试为准,不要求每轮执行 Native 编译。Native 验证安排在阶段性验收
或新增反射、JNDI、TLS 等兼容性风险时集中进行;记录尚未覆盖的新路径。
## Native 与监控
- CI 构建 Native 产物并对该产物执行集成测试;测试报告区分 JVM 与 Native。