接入 Hydra 授权、客户端管理与统一注销
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
# OAuth2/OIDC 客户端管理
|
||||
|
||||
本服务通过受限 API 管理 Hydra 中的第一方 confidential authorization-code 客户端。
|
||||
API 没有管理 UI,沿用浏览器 Spring session;调用者必须完成有效的密码 + WebAuthn MFA,
|
||||
且直接属于配置的管理组。默认组列表为空,拒绝全部管理操作。
|
||||
|
||||
```yaml
|
||||
iam:
|
||||
clients:
|
||||
admin-group-dns:
|
||||
- CN=IAM Administrators,CN=Users,DC=example,DC=test
|
||||
```
|
||||
|
||||
按完整 DN 匹配,保持与 AD 仓储一致的直接组语义,不展开嵌套组。可配置一个统一粗粒度
|
||||
管理组,不要求每个应用建立独立 admins 组。组成员来自本次目录登录快照,变更后需重新认证。
|
||||
|
||||
| 方法与路径 | 行为 |
|
||||
| --- | --- |
|
||||
| GET `/api/iam/session` | 获取当前 session 的 CSRF headerName/token |
|
||||
| GET `/api/iam/clients?page=0&size=20` | 返回 Hydra 分页内支持的客户端,size 1–100 |
|
||||
| POST `/api/iam/clients` | 创建,201 返回 `{client, secret}`,密钥仅此次返回 |
|
||||
| GET `/api/iam/clients/{id}` | 查询,不返回密钥 |
|
||||
| PUT `/api/iam/clients/{id}` | 替换可管理字段,保留现有密钥 |
|
||||
| DELETE `/api/iam/clients/{id}` | 删除,204 |
|
||||
|
||||
写操作必须携带当前 session cookie 与 GET session 返回的 CSRF 请求头。匿名返回 401,
|
||||
因素不足、缺少管理组或 CSRF 不符返回 403。CSRF 默认由 Spring Security 处理,没有绕过路径。
|
||||
成功变更记录 action、client ID 和操作者稳定目录标识,不记录密钥。没有 bearer 管理接口。
|
||||
|
||||
POST/PUT 请求示例(PUT 的 id 必须等于路径):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "example-app",
|
||||
"name": "示例应用",
|
||||
"redirectUris": ["https://app.example.test/oidc/callback"],
|
||||
"scopes": ["openid", "profile", "email", "groups"],
|
||||
"postLogoutRedirectUris": ["https://app.example.test/logged-out"],
|
||||
"backchannelLogoutUri": "https://app.example.test/oidc/backchannel-logout",
|
||||
"frontchannelLogoutUri": "",
|
||||
"loginEnabled": true
|
||||
}
|
||||
```
|
||||
|
||||
回调必须 HTTPS(隔离开发允许 loopback HTTP),不允许通配符、fragment 或 URL 用户信息。
|
||||
本轮固定 `authorization_code`、`code`、`client_secret_basic`、public subject;scope 限于上述
|
||||
四项且要求 openid。不接收任意 Hydra 字段、密钥、签名配置或授权类型,也没有通用 Admin API
|
||||
代理。不要为并不支持注销协议的应用登记虚构的端点。
|
||||
|
||||
客户端与生成的 secret 由 Hydra 持久化;本服务不复制或缓存它们。管理员应在创建时安全保存
|
||||
secret。暂不提供密钥轮换接口。Hydra 版本固定,更新时省略 secret 以保留原值;集成测试验证
|
||||
创建、重启后读取、更新后原密钥仍能认证、删除。测试 PostgreSQL 完全独立于真实开发 MFA 库。
|
||||
|
||||
列表按 Hydra 原始分页过滤不支持的授权类型,空页不表示之后必无客户端;本接口不是已有
|
||||
全部 Hydra 客户端类型的迁移工具。禁用写入 Hydra metadata,后续授权即时重读并拒绝,
|
||||
已签发 token 的生命周期另由 issuer 和应用控制。
|
||||
@@ -0,0 +1,131 @@
|
||||
# Hydra Login/Consent 接入
|
||||
|
||||
本实现使用 Hydra 的私有 Admin API,沿用 Spring Security 的 AD + WebAuthn 双因素认证。
|
||||
OAuth2/OIDC 签发、客户端注册表和客户端密钥由 Hydra 持有。应用只负责身份、授权确认与
|
||||
Login/Consent 接受,不自行签发 token,也不实现另一套认证状态机。
|
||||
|
||||
## 浏览器路径
|
||||
|
||||
1. 客户端发起 Hydra authorization code 请求;Hydra 将浏览器带到 `/oauth2/start?login_challenge=...`。
|
||||
2. 服务端通过 Admin API 核对 client、scope、audience 与 issuer 请求地址,绑定浏览器会话,
|
||||
再进入 `/oauth2/login`。该页面由 Spring Security 要求两种因素,未满足时转到密码或 MFA 页。
|
||||
3. MFA 完成后显示应用和申请范围;用户通过带 CSRF 的原生表单 POST 确认。
|
||||
4. 服务端重新核对 Hydra 请求,使用显式 subject 绑定接受 login,浏览器返回 Hydra。
|
||||
5. Hydra 回到 `/oauth2/consent`;服务端核对会话随机值、原始 challenge 摘要、主体、client、
|
||||
原始请求 URL 和 scope 后一次性接受 consent;Hydra 将授权码交给客户端。
|
||||
|
||||
只为管理员启用的第一方 client 接受 openid/profile/email/groups,不授予 access-token audience、
|
||||
不申请 offline_access/refresh token。Claims 按请求 scope 释放,组保持直接 AD memberOf 的名称;
|
||||
AD mail 没有邮箱所有权验证依据,`email_verified=false`。
|
||||
|
||||
单个浏览器会话只保存一个未完成请求,10 分钟失效,新请求替换旧请求。认证因素与会话
|
||||
依然由 Spring Security 管理;`HydraBrowserRequests` 只保存待接受的 issuer 请求及回程关联,
|
||||
不记录密码、私钥或 token。退出/重启后未完成请求需从应用重新发起。
|
||||
|
||||
Hydra v26 的 consent `login_challenge` 是内部标识,不能与浏览器收到的 opaque challenge
|
||||
逐字比较。本服务在受信任的 login accept `context` 中写入原始 challenge 的 SHA-256 摘要
|
||||
与会话随机值,在 consent 读回并核对;不解析 Hydra 内部格式。
|
||||
|
||||
确认页的 CSP 仅增加当前 Hydra 与已校验回调的 origin,允许原生表单返回 issuer 后跳转;
|
||||
身份页允许向固定 Hydra origin 完成注销跳转。Hydra 返回的 redirect 必须是已配置公共 origin 下的
|
||||
`/oauth2/auth`,Admin HTTP client 不跟随重定向,不向浏览器传播上游错误正文。
|
||||
|
||||
本轮仅覆盖交互式授权码流,不支持静默 `prompt=none`。
|
||||
接受 login 时保留 Hydra 登录会话,供统一注销关联应用;会话寿命沿用 Hydra 配置,
|
||||
不延长已有会话。Hydra 的 `skip` 仅表示 issuer 记得登录,不能绕过 Spring 的有效双因素、
|
||||
原生表单确认与主体匹配。`prompt=login/consent/select_account` 或 `max_age` 存在时重新验证身份,
|
||||
不把之前的 MFA 时间改写为新登录时间。不请求上述参数时可复用仍有效的本地双因素会话。
|
||||
|
||||
## 配置与主体连续性
|
||||
|
||||
先启用 [AD](ad-login.md) 与 [WebAuthn](webauthn.md),再提供以下配置:
|
||||
|
||||
```yaml
|
||||
iam:
|
||||
hydra:
|
||||
enabled: true
|
||||
admin-url: http://hydra-admin.hydra.svc.cluster.local:4445
|
||||
public-url: https://hydra.ad.ddupan.top
|
||||
clients: [gitea] # 仅供尚未写入管理标记的旧客户端过渡
|
||||
subjects:
|
||||
- authority: ad.example.test
|
||||
directory-id: 00112233-4455-6677-8899-aabbccddeeff
|
||||
subject: human:EXISTING_REVIEWED_SUBJECT
|
||||
```
|
||||
|
||||
以上主体是格式示例,不能用于真实账号。配置中的绑定按 AD authority + objectGUID 匹配,
|
||||
必须唯一;未绑定用户拒绝授权。不使用用户名、邮箱、自动创建 Gitea 账号或 AD GUID 的新哈希
|
||||
来替代现有主体。现役 Go 适配器使用 `human:` + SHA-256(Authelia issuer + NUL + sub),
|
||||
上线前需要取得并核对该旧主体,建立到 AD 稳定键的显式映射,确认 Gitea 的外部账号关联。
|
||||
普通改名不改变绑定;删除或修改绑定属于迁移操作,需要单独审查。
|
||||
|
||||
Hydra 环境配置需将 login URL 指向 `/oauth2/start`,consent URL 指向 `/oauth2/consent`,
|
||||
logout URL 指向 `/oauth2/logout`,默认 post-logout URL 指向本服务 `/signin`。
|
||||
本仓库的实现与测试不等于这些生产设置已变更。Admin URL 可以使用现役受 NetworkPolicy
|
||||
约束的集群内 HTTP,也可通过 loopback port-forward;不能公开管理端口。
|
||||
公共 origin 必须 HTTPS,HTTP 只接受隔离测试的 loopback。客户端请求必须显式带 redirect_uri。
|
||||
|
||||
## 客户端注册与管理边界
|
||||
|
||||
`clients` 领域模块提供受 Spring Security 保护的 CRUD,调用私有 Hydra Admin API。
|
||||
Hydra PostgreSQL 是客户端与密钥的唯一持久化来源,不读其内部表、不建第二份注册表。
|
||||
使用方式和边界见 [客户端管理接口](client-management.md)。
|
||||
|
||||
每次 login/consent 都重新读取 client 的 `metadata.iam_login_enabled`;显式 false 拒绝新授权。
|
||||
仅当标记不存在时使用旧 `iam.hydra.clients` allowlist。通过 API 新增启用的客户端不需要修改
|
||||
服务配置。禁用不能撤回已签发 token 或已建立的应用会话。公共动态注册入口不在本轮范围,
|
||||
不能让未信任调用者写入该管理标记。
|
||||
|
||||
## 统一注销
|
||||
|
||||
RP 使用 Hydra discovery 的 `end_session_endpoint`,携带 ID token hint 与已登记回调。
|
||||
Hydra 查询其登录会话后回到 `/oauth2/logout`;用户提交有 CSRF 与浏览器请求绑定的确认表单,
|
||||
服务端重新核对 challenge、主体和登记回调,接受 Hydra logout,并通过 Spring 的
|
||||
`SecurityContextLogoutHandler` 清除当前本地会话。Hydra 负责通知登记的 front/back-channel
|
||||
端点并返回应用;不自行遍历客户端发 HTTP 请求。身份页也提供原生表单发起统一退出。
|
||||
|
||||
Hydra 会话不存在或已过期时可能直接返回应用,不经过确认页;这不证明 Spring 会话也被清除。
|
||||
本地退出按钮仍能清除当前 Spring 会话。下游必须实现登记的注销协议,通知失败也不能宣称
|
||||
应用已退出。统一注销不等于撤销所有已签发 token,不覆盖其他浏览器设备。
|
||||
|
||||
## 正式域名规划
|
||||
|
||||
正式入口目标为 `https://auth.ddupan.top`,替换现有 Authelia 入口,Hydra 与本服务按路径同源:
|
||||
|
||||
- Hydra:discovery/JWKS、`/oauth2/auth`、`/oauth2/token`、`/oauth2/revoke`、
|
||||
`/oauth2/sessions/logout`、`/userinfo` 等经核对的 public 端点。
|
||||
- iam-login:`/signin`、`/signin/**`、`/webauthn/**`、`/login/webauthn`、
|
||||
`/oauth2/start`、`/oauth2/login`、`/oauth2/consent`、`/oauth2/logout`、`/api/iam/**` 和静态资源。
|
||||
- 不把整个 `/oauth2/**` 路径交给 Hydra;不发布 Hydra `/admin/**` 或管理端口。
|
||||
|
||||
这是部署计划,不是现网配置。上线前需核对 cookie 名称、受信任代理与 TLS 转发、issuer
|
||||
变更和应用配置、旧 sub 关联,以及新 WebAuthn RP ID 的凭据注册。不能仅改 DNS 后假定现有
|
||||
passkey 和 OIDC 会话仍可复用;回退路径也需在基础设施 PR 中明确。
|
||||
|
||||
## 隔离验证与生产切换
|
||||
|
||||
首轮验收以现有 AD + MFA → Hydra → Gitea 原账号及仓库权限为目标,不以完整 self-service、
|
||||
目录管理或自动恢复为前置条件。AD 管理与人工 MFA 恢复边界见 [第二因素](webauthn.md)。
|
||||
|
||||
```sh
|
||||
scripts/gradle-in-docker test bootJar
|
||||
scripts/gradle-in-docker hydraBrowserFixture
|
||||
# 另一终端,仅连接固定的 loopback 测试夹具:
|
||||
IAM_HYDRA_FIXTURE=1 npm --prefix frontend run test:browser -- hydra.spec.ts
|
||||
```
|
||||
|
||||
夹具包含模拟 AD、临时 PostgreSQL、固定 digest 的 Hydra v26.2.0,以及测试专用 OAuth client。
|
||||
Hydra 只监听 127.0.0.1:14444/14445,应用使用测试证书监听 HTTPS localhost:18083;
|
||||
客户端回调由测试专用 loopback HTTP 服务接收,不在生产 JAR 中加入测试回调或 token 查看入口。
|
||||
虚拟认证器产生真实 WebAuthn 签名,随后交换授权码,检查 ID token 的 JWKS 签名、issuer、
|
||||
audience、nonce、有效期、预配置旧 sub、组与邮箱语义,并拒绝授权码重放。
|
||||
同时验证 remembered login 仍显示授权确认、RP 发起注销、back-channel token 签名与 sid
|
||||
关联,以及 Spring 会话失效。JVM 集成测试另验证真实 PostgreSQL 上客户端 CRUD、Hydra
|
||||
重启后记录仍在、更新保留旧密钥与管理接口的 MFA/组/CSRF 拒绝。
|
||||
这些验证不等于生产 Gitea 账号关联、真实新链路人类操作或 Native 验收。
|
||||
|
||||
下一步生产切换仍需完成真实 subject 映射审查、AD/WebAuthn/Hydra Native 验收、部署网络规则,
|
||||
以及从 Gitea 返回原账号与原仓库权限的实际验收。现役 Go/Authelia 登录入口继续作为已验收路径。
|
||||
|
||||
上游接口依据:[Hydra Login/Consent](https://www.ory.com/docs/oauth2-oidc/custom-login-consent/flow)、
|
||||
[客户端管理能力](https://www.ory.com/hydra)。
|
||||
@@ -31,6 +31,8 @@ WebAuthn 集成;TOTP、恢复方式与已有 Authelia MFA 的迁移方式需
|
||||
不能假设运行时修改属性会重新装配 bean。本轮只验证 JVM,尚未验收该 Native 路径。
|
||||
- WebAuthn 新增的 JDBC/Flyway/PostgreSQL、WebAuthn4J 校验与 JSON 路径本轮仅做 JVM 验证;
|
||||
Native AOT 时还需启用 `iam.webauthn.enabled`,不得套用基础骨架的 Native 结论。
|
||||
- Hydra 的 RestClient/JDK HTTP 与 JSON DTO 新增反射绑定声明;仍需在启用 `iam.hydra.enabled`
|
||||
的 Native 产物上实际运行授权码流程,JVM 接入结果不构成该项验收。
|
||||
- 最终运行镜像无需 JRE,不允许以回退 JVM 的方式令 Native 验收通过。
|
||||
- LDAP、MFA、数据库、TLS、JSON 和 Hydra HTTP 客户端全部在 Native 中执行。
|
||||
- 纳入 Actuator、Micrometer Prometheus 与 OpenTelemetry/分布式追踪;实际发起请求后
|
||||
|
||||
+7
-5
@@ -9,11 +9,13 @@ AD 仍为用户与组权威;凭据按目录 authority + objectGUID 关联,
|
||||
1. `/signin` 使用原生表单 POST 验证 AD 密码,建立 `FACTOR_PASSWORD`。
|
||||
2. `/signin/mfa`:没有凭据时注册 passkey;已有凭据时验证 passkey。
|
||||
3. 注册只保存凭据,必须再次实际验证,才取得 `FACTOR_WEBAUTHN`。
|
||||
4. `/signin/complete` 要求两种因素均在 10 分钟内有效;目前仅显示验证结果,尚不接受 Hydra challenge。
|
||||
4. `/signin/complete` 要求两种因素均在 10 分钟内有效;单独访问显示验证结果;存在 Hydra 请求时继续 [Login/Consent](hydra-login.md)。
|
||||
|
||||
首次注册信任近期 AD 密码验证。已有凭据后的新增注册同时要求密码与 WebAuthn 因素;
|
||||
本轮 UI 只提供首次注册与验证,不提供新增管理、删除或自助恢复入口。遗失所有 passkey
|
||||
尚无自助登录途径;生产上线前需要另行确定恢复和初始注册政策,不能把数据库清空作为日常恢复方式。
|
||||
本轮 UI 只提供首次注册与验证,不提供新增管理、删除或自助恢复入口。按维护者确认的
|
||||
自用范围,遗失全部 passkey 由管理员人工操作数据库恢复,不以开发恢复 UI 作为上线条件。
|
||||
人工恢复只处理核实后的目标主体凭据,并按首次注册规则重新绑定;不能清空整个凭据库。
|
||||
AD 用户、密码和组继续通过 RSAT 或目录命令行管理,不在本应用增加目录管理页面。
|
||||
注册和验证均要求认证器 user verification(例如 PIN 或生物识别)。
|
||||
|
||||
Spring Security 负责因素合并、会话轮换、退出和 CSRF。应用仅补目录主体与凭据所有权
|
||||
@@ -66,5 +68,5 @@ IAM_WEBAUTHN_FIXTURE=1 npm --prefix frontend run test:browser -- webauthn.spec.t
|
||||
```
|
||||
|
||||
测试专用启动类仅在 test classpath,不进入生产 JAR,也不提供生产调试 API。
|
||||
真实用户的 passkey 注册、认证器兼容性和 Native 路径仍需独立验收;JVM/虚拟认证器通过
|
||||
不能代替真实人类或 Native 验收。生产共享 PostgreSQL 的接入留在部署阶段。
|
||||
维护者已确认开发入口的 AD + passkey 人类路径能够工作。更多认证器兼容性和 Native 路径
|
||||
仍需独立验收,不能套用虚拟认证器结果。生产共享 PostgreSQL 的接入留在部署阶段。
|
||||
|
||||
Reference in New Issue
Block a user