Files
zyt/docs/plans/ai-mcp-sso-2026-10-10.md
T
2026-10-10 10:55:32 +08:00

54 lines
4.8 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.
# 用甄养堂账号登录行知(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 代码 + 行知登录页,账号密码与一键两种方式)也已通过。