104 lines
5.4 KiB
Markdown
104 lines
5.4 KiB
Markdown
# 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 前
|
||
即拒绝的合成外域用户名,不尝试猜测人类密码。浏览器回归共 1 项通过,包含移动端布局。
|
||
复现:`IAM_AD_URL=https://验收域名:端口 npm --prefix frontend run test:browser -- ad-login.spec.ts`。
|
||
实际人类密码与组结果仍需维护者
|
||
在 HTTPS 页面输入凭据验收,不在聊天、命令行或日志中传递人类密码。
|
||
新增 AD 路径尚未进行 Native 测试,不能复用旧 UI 原型的 Native 结论。
|