54 lines
4.8 KiB
Markdown
54 lines
4.8 KiB
Markdown
# 用甄养堂账号登录行知(AI 模块单点登录,2026-10-10)
|
||
|
||
行知(AI 工作助手)的登录页增加“使用甄养堂账号登录”:跳到甄养堂的登录确认页,确认后回到行知,行知首次登录自动开通成员账号,并同时完成“绑定甄养堂账号”(只读授权)。密码只在甄养堂输入和校验,不经过行知。
|
||
|
||
## 改动范围(都在 AI 模块 `server/app/mcp` 内,原有后台接口、Logic、中间件、配置未改)
|
||
|
||
| 文件 | 说明 |
|
||
|---|---|
|
||
| `controller/SsoController.php`(新增) | `GET /mcp/sso/authorize` 登录确认页;`POST /mcp/sso/whois`、`/approve`、`/login` 页面用;`POST /mcp/sso/token` 行知服务器换取结果 |
|
||
| `service/SsoService.php`(新增) | 客户端与回调地址校验、浏览器里已登录后台会话的识别、一次性授权码(2 分钟、只能用一次,文件锁防并发兑换) |
|
||
| `service/GrantService.php`(AI 模块自身) | `issue()` 拆成 `verifyPassword()` + `issueFor()`,`assertAdminUsable()` 改为 public;绑定接口 `/mcp/auth/grant` 的行为不变 |
|
||
| `service/McpConfig.php`(AI 模块自身) | 新增 `ssoEnabled / ssoClientId / ssoClientSecret / ssoRedirectUris` |
|
||
| `server/tests/AiMcpSsoTest.php`(新增) | 见“测试” |
|
||
|
||
不需要执行新的数据库迁移;不新增权限点(沿用 `ai.mcp/access`)。
|
||
|
||
## 流程与门禁
|
||
|
||
1. 行知把浏览器带到 `/mcp/sso/authorize?client_id&redirect_uri&state`。`client_id`、`redirect_uri` 必须与 .env 登记的完全一致,否则只显示原因、不显示登录框、不跳转。
|
||
2. 确认身份,两种方式:
|
||
- 浏览器里已登录甄养堂后台(localStorage `like_admin_token`):显示“你已登录甄养堂后台:某某”,点“以此账号登录”。只认电脑端、手机端的正常登录(terminal 1/2),AI 后台浏览器(8)、企微客服端(7/9)和过期会话一律不认。
|
||
- 输入账号密码:与“绑定甄养堂账号”同一套校验(按 IP 限流、连续错误锁定、停用、企微强制绑定、`ai.mcp/access` 权限点、未改初始密码)。
|
||
3. 带一次性授权码回到行知;行知服务器用客户端密钥调用 `/mcp/sso/token`(经过 `ALLOWED_IPS` / Origin 检查),得到账号信息和只读令牌(与绑定签发的授权相同,同一行知实例的旧授权自动作废)。
|
||
4. 页面:严格 CSP(脚本只认本页 nonce、`frame-ancestors 'none'`、`form-action 'none'`)、`X-Frame-Options: DENY`、`Cache-Control: no-store`、`Referrer-Policy: no-referrer`,并覆盖全局中间件的 `Access-Control-Allow-Origin: *`。
|
||
5. 审计:`zyt_ai_access_log` 的 `sso.approve`、`sso.login`(拒绝时)、`sso.token`。
|
||
|
||
## 上线步骤
|
||
|
||
1. 同步上面四个文件到生产(`SsoController.php`、`SsoService.php`、`GrantService.php`、`McpConfig.php`)。
|
||
2. 在行知管理端 → 组织连接器 → 甄养堂业务系统 → “账号登录行知” → “生成密钥”,把显示的四行加到生产 `.env` 的 `[AI_MCP]` 段(与 `ENABLED = true` 同一段),例如:
|
||
|
||
```ini
|
||
SSO_ENABLED = true
|
||
SSO_CLIENT_ID = xingzhi
|
||
SSO_CLIENT_SECRET = (行知管理端生成,只显示一次)
|
||
SSO_REDIRECT_URIS = http://xz.zhenyangtang.cn:8787/api/auth/sso/zyt/callback
|
||
```
|
||
|
||
3. 重新加载 PHP(PHP-FPM / 宝塔里重载或重启 PHP),然后在行知管理端勾选“开启:登录页显示这个按钮”并保存。
|
||
4. 如设置了 `ALLOWED_IPS`,需包含行知服务器的出口 IP(`/mcp/sso/token` 由行知服务器调用);登录确认页本身面向员工浏览器,不受这项限制。
|
||
5. 停用:把 `SSO_ENABLED` 改为 false(或删掉这几行)并重载 PHP;行知侧也可在管理端取消勾选。
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
source ~/.cache/zyt-tools/scripts/zyt_env.sh # 本地 PHP 8.2 + MariaDB(zyt_mcp_test)
|
||
export PHP_AI_MCP_SSO_ENABLED=true PHP_AI_MCP_SSO_CLIENT_ID=xingzhi
|
||
export PHP_AI_MCP_SSO_CLIENT_SECRET=<至少 32 位> PHP_AI_MCP_SSO_REDIRECT_URIS=http://127.0.0.1:18787/api/auth/sso/zyt/callback
|
||
# 服务端与测试进程使用同一组 SSO 配置
|
||
AI_MCP_TEST_MYSQL=1 AI_MCP_TEST_BASE_URL=http://127.0.0.1:8099 $PHP server/tests/AiMcpSsoTest.php
|
||
```
|
||
|
||
覆盖:确认页只对登记的客户端显示(安全响应头、未登记地址不回显)、账号密码确认的各项门禁、一键确认只认电脑端/手机端会话、授权码要客户端密钥并与回调地址一致且只能用一次、确认后被停用的账号兑换失败、换来的令牌可直接使用并替换同一实例的旧授权、审计、原有绑定接口不受影响。原有 `AiMcpUnitTest`、`AiMcpHttpContractTest`、`AiMcpConsoleTest`、`AiMcpPerfTest` 回归通过。与行知的端到端联调(真实 zyt 代码 + 行知登录页,账号密码与一键两种方式)也已通过。
|