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

4.8 KiB
Raw Blame History

用甄养堂账号登录行知(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 同一段),例如:

    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;行知侧也可在管理端取消勾选。

测试

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 代码 + 行知登录页,账号密码与一键两种方式)也已通过。