74 lines
5.4 KiB
Markdown
74 lines
5.4 KiB
Markdown
# 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 事务 Cookie;state、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
|
||
```
|