Files
zyt/docs/iam-quick-login.md
T

74 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# IAM 统一账号快捷登录
在原账号密码、企业微信入口之外提供可关闭的 OIDC 登录方式。不开启配置时不访问 IAM、不创建事务文件,也不改变原登录行为。
## 配置
在服务器私密 `server/.env` 增加:
```ini
[IAM]
ENABLED = false
ISSUER = https://login.zhenyangtang.com.cn/auth/realms/iam-hub
CLIENT_ID = zyt
CLIENT_SECRET = CONFIGURE_PRIVATE_CLIENT_SECRET
REDIRECT_URI = https://admin.zhenyangtang.com.cn/adminapi/iam/callback
API_URL = https://login.zhenyangtang.com.cn/iam-api
APPLICATION_ID = zyt
APPLICATION_TOKEN = CONFIGURE_PRIVATE_APPLICATION_TOKEN
PUBLIC_URL = https://admin.zhenyangtang.com.cn
```
Keycloak 的 `zyt` client 仅登记上述精确 HTTPS 回调,开启 Authorization Code 与 PKCE S256;不启用密码授权。配置完成后再设 `ENABLED=true`
## 账号和权限
- IAM 必须存在有效员工、`zyt` 应用授权和经管理员确认的账号绑定。`externalAccountId` 是已存在的甄养堂管理员 ID,不是账号名称。
- 不按用户名或邮箱自动合并,不自动生成 root、不改已有角色/部门/数据范围。
- 本次新员工按用户指定的 **医助角色 ID 2** 开通,沿用原新增账号流程(含 IM 账号导入),再显式绑定到 IAM。后续人员也需由管理员明确开通与绑定;没有绑定时快捷登录拒绝,而非猜测账号。
- 登录签发原 `AdminTokenService` token,继续遵守本地 disable、软删除、单点/多点登录、首次改密以及强制绑定企微规则。
- 新账号默认 `is_paw=0`。完成首次改密、绑定企微之前不会跳过原有业务门禁。
- 此版本在每次新快捷登录时检查 IAM 状态,不批量撤销既有业务会话,也不宣称已实现 IAM 停权事件实时踢下线。
## 协议与部署
端点为 `/adminapi/iam/config``start``callback``exchange`。浏览器保存独立 HttpOnly/Secure/SameSite=Lax 事务 Cookiestate、nonce、PKCE 与一次性兑换码绑定浏览器。URL 中不传业务 token,兑换只能 POST 且校验来源。事务文件保存在应用 runtime 的 `iam-login` 私有子目录,0600,受文件锁保护。当前单服务器 PHP-FPM 多进程共享该目录;扩展到多机前须换成共享原子存储。
JWT 使用锁定的 firebase/php-jwt,校验 RS256、签名、issuer、audience/azp、有效期和 nonce;出站 TLS 校验保持开启,不跟随重定向。
前端通过服务器开关显示按钮,兑换后复用现有 token 缓存与页面分流,IAM 失败时原账号及企微入口仍可使用。
发布静态文件保留旧哈希资源,最后更新入口,避免已打开页面加载旧资源时报错。
## 验证与回退
```sh
php server/tests/IamOidcClientTest.php
php server/tests/IamLoginTransactionTest.php
php server/tests/AdminDesktopAuthContractTest.php
php server/tests/AdminDesktopSessionBehaviorTest.php
php server/tests/AdminMultiRoleRegressionTest.php
php server/tests/DataScopeMultiRoleTest.php
NODE_PATH=admin/node_modules node server/tests/IamLoginUiContractTest.mjs
```
回退时先关闭 IAM 开关,再恢复本次代码和静态入口备份,不清空会话、用户、角色、业务数据库或全部缓存。新创建的员工与医助账号保留,由管理员决定是否停用。
## 2026-09-10: authorized first-login provisioning
Deploy `server/database/migrations/20260910_iam_local_identity.sql` before the adapter update (adjust the `zyt_` prefix only when configured differently). This additive InnoDB ledger is keyed by SHA-256 of the exact issuer/application/subject tuple and uniquely associates a new local account. Keep the ledger when rolling code back; never drop it or delete real accounts as a code rollback.
The authenticated provisioning-context API is the authority for active employee + application grant. Only an explicit `shouldCreateLocalAccount=true` with an empty external ID creates an account. The local transaction inserts the immutable ledger, a random local account name and password, and role **2 / 医助** only; a missing, deleted, renamed or disabled default role fails closed. Neither name, phone nor email merges accounts. Existing explicit bindings use existing permissions without modification. Password setup (`is_paw=0`), non-root status, and original WeCom binding gates remain intact. The normal physician IM import runs best-effort after central binding confirmation, matching `AdminLogic::add` semantics; provider errors are logged by the existing helper.
After local commit, the adapter uses application authentication and stable idempotency metadata to POST bindings, accepts HTTP 201, then reads provisioning-context again and requires the exact created ID before issuing a login ticket. Remote failures keep the local ledger for retry, with no business token. Parallel workers cannot create another ledger/account for that immutable identity. A transient DB deadlock can fail one login attempt; retry resumes the winning account.
The IAM server must reject changing an existing binding to a different external ID on this endpoint (rather than an upsert overwrite). Ship that conflict fix before this adapter. Admin-driven deliberate rebindings require a separate verified workflow.
Focused local tests:
```sh
php server/tests/IamOidcClientTest.php
php server/tests/IamLoginTransactionTest.php
php server/tests/IamProvisioningTest.php
# Dedicated disposable local MariaDB only; drops four fixture tables in iam_fixture.
IAM_TEST_MYSQL=1 php server/tests/IamProvisioningTest.php
```