CRM 后端与 MySQL 接入 IAM 对接文档(历史参考)
CRM 后端与 MySQL 接入 IAM 对接文档
Section titled “CRM 后端与 MySQL 接入 IAM 对接文档”当前状态(2026-10-30)
Section titled “当前状态(2026-10-30)”本文仅作历史方案参考,不能作为 CRM 当前路由、环境变量、数据库字段或角色映射的配置依据。CRM 后端已反馈实际方案与本文不同,包括 start / exchange / backchannel-logout、IAMOIDC*、iam_session_id、前端 ticket 回调和角色映射。实际行为以 CRM 后端维护的实现和对接说明为准。
CRM 后端本次确认的测试环境配置:
| 配置项 | 当前值 |
|---|---|
| Redirect URI | https://crm-api.dev.ndd-x.cn/api/auth/iam/callback |
| 同步接收地址 | https://crm-api.dev.ndd-x.cn/api/internal/iam/provisioning/users |
| Token Endpoint Auth Method | client_secret_basic |
| PKCE | S256 |
| Scope | openid profile email |
| Back-Channel Logout URI | 留空,CRM 当前不接收 |
以上为配置依据,更新文档不会修改现有环境中的客户端配置。账户同步和准入快照以账户与访问权限同步协议为准。以下正文保留供历史查阅。
1. 文档目的
Section titled “1. 文档目的”本文交付给 CRM 后端同事,用于把旧版 CRM Laravel 后端和 CRM MySQL 数据库接入当前 IAM,实现企业统一身份登录。
本次涉及两个独立系统,必须先明确边界:
| 系统 | 技术栈 | 负责人 | 本次职责 |
|---|---|---|---|
| IAM | Better Auth 1.7.1、Hono、PostgreSQL | IAM 负责人 | 创建和配置 CRM OAuth/OIDC Client,提供 issuer、Client ID、Client Secret 和 Claims;必要时修改 IAM 数据库 |
| CRM API | Laravel 9、Sanctum、Spatie Permission | CRM 后端同事 | 实现 OIDC 登录、Token 校验、用户绑定、CRM Token 签发、Back-Channel Logout |
| CRM 数据库 | MySQL | CRM 后端同事 | 执行 Laravel migration,修改 users 和 personal_access_tokens |
| CRM Web | Vue 3 | CRM 前端负责人 | 发起整页登录跳转、接收一次性 ticket、换取并保存 CRM Sanctum Token |
本文不会要求 CRM 后端同事修改 IAM PostgreSQL,也不会要求 IAM 负责人直接修改 CRM MySQL。
2. 当前基线与目标结论
Section titled “2. 当前基线与目标结论”2.1 代码基线
Section titled “2.1 代码基线”| 项目 | 基线 |
|---|---|
| IAM commit | 7fa227324f0fac6af12fbad0172c1453f29aaf22 |
| CRM API 原仓库 commit | 765c42049bac02f1bd3d17ebbf590304a79f69e6 |
| CRM API | PHP ^8.0.2、Laravel ^9.19、Sanctum ^3.0 |
| IAM | Better Auth 1.7.1,OAuth 2.1/OIDC Provider |
| IAM JWT 算法 | RS256,RSA 2048 |
| CRM 数据库 | MySQL |
CRM API 工作区中已有一套目标实现作为参考,主要文件见第 10 节。后端同事应基于自己的后端分支实现、审查并执行 migration,不能把本地未提交文件直接当作已上线结果。
2.2 方案结论
Section titled “2.2 方案结论”采用服务端 BFF 模式:
- CRM Web 不保存 OIDC Client Secret。
- CRM Web 不直接用 Authorization Code 交换 Token。
- CRM API 使用 Authorization Code + PKCE S256 与 IAM 通信。
- CRM API 必须验证 ID Token 签名及 OIDC Claims。
- IAM 负责证明用户身份和 CRM 应用准入。
- CRM 继续使用自己的
users、Sanctum Token、Spatie 角色、团队和业务权限。 - IAM Access Token 不能作为 CRM 业务接口的 Bearer Token。
- IAM 决定 CRM 准入并同步企微部门和职位;CRM 在管理页配置部门 / 职位到本地 Spatie 角色的规则。
- IAM 自动创建的账户持续同步映射角色;已有 CRM 账户保留原角色、团队和客户归属。
- 首次登录通过
POST /api/internal/provisioning/snapshot拉取当前准入快照,再经统一账户应用逻辑建号或同步;不以旧 ID Token 重新启用账户。
sequenceDiagram
autonumber
participant U as 浏览器
participant W as CRM Web
participant C as CRM API
participant I as IAM
participant M as CRM MySQL
U->>W: 点击统一身份登录
W->>C: GET /api/auth/iam/start?return_to=/...
C->>C: 生成 state、nonce、PKCE 并写共享缓存
C-->>U: 302 IAM authorize
U->>I: 企业微信登录并授权
I-->>C: 302 callback?code=&state=
C->>I: 服务端用 code + verifier 换 Token
C->>I: 拉取 JWKS 并验证 ID Token
C->>I: 使用 Access Token 请求 UserInfo
C->>M: 匹配并绑定 CRM 用户
C-->>U: 302 CRM Web /iam/callback?ticket=...
W->>C: POST /api/auth/iam/exchange
C->>M: 创建带 IAM sid 的 Sanctum Token
C-->>W: 返回 CRM Token 和本地角色
3. IAM 负责人需要提供的内容
Section titled “3. IAM 负责人需要提供的内容”本节由 IAM 负责人完成,CRM 后端同事只负责提供 CRM API/Web 的实际地址并参与核对。
3.1 创建 CRM OIDC Client
Section titled “3.1 创建 CRM OIDC Client”建议通过 IAM 管理端创建,不建议直接手写 IAM 数据库记录。配置如下:
| 配置项 | 值 |
|---|---|
| Client Name | CRM |
| Client 类型 | 机密客户端 |
| Redirect URI | https://<crm-api-domain>/api/auth/iam/callback |
| Token Endpoint Auth Method | client_secret_basic |
| Grant Types | authorization_code |
| Response Types | code |
| Scope | openid profile email(需要 refresh token 时再加 offline_access) |
| Require PKCE | true,只接受 S256 |
| Back-Channel Logout URI | https://<crm-api-domain>/api/auth/iam/backchannel-logout |
| Back-Channel Logout Session Required | true |
| Skip Consent | 按产品要求;内部系统可评估开启 |
Back-Channel Logout URI 在生产必须是外部可访问的 HTTPS 地址,不能带凭据和 fragment。本地开发无法直接接收 IAM 的服务器间回调时,可先验证登录主流程,再在可公网访问的测试环境验收统一下线。
Redirect URI 必须逐字符一致。协议、域名、端口、路径或末尾 / 任一不同都会导致授权失败。
3.2 交付给 CRM 后端的参数
Section titled “3.2 交付给 CRM 后端的参数”IAM 负责人通过安全渠道提供:
IAM_OIDC_ISSUER=https://iam.cq-i.cn/api/authIAM_OIDC_CLIENT_ID=<crm-client-id>IAM_OIDC_CLIENT_SECRET=<crm-client-secret>Client Secret 只允许进入 CRM API 的 Secret 管理系统,不得发给 CRM Web,不得出现在仓库、群聊截图、日志或监控事件中。
3.3 IAM Claims 契约
Section titled “3.3 IAM Claims 契约”CRM 需要使用以下 Claims:
| Claim | 用途 |
|---|---|
sub |
IAM 稳定用户标识,绑定后作为 CRM 的首要匹配键 |
sid |
IAM 会话标识,用于 Back-Channel Logout 精确撤销 CRM Token |
email |
首次绑定 CRM 用户的候选邮箱 |
email_verified |
只有严格布尔值 true 才允许邮箱首次绑定 |
wecom_user_id |
优先绑定已有 CRM 企业微信用户 |
department_id |
IAM 侧主部门 ID,仅用于排障与日志;业务判定请用开通快照中的 departmentIds |
app_role |
历史兼容字段;CRM 不再用它分配业务角色,改用开通快照中的企微部门和职位 |
IAM 当前使用 RS256 签发 ID Token 和 Logout Token。CRM 可保留 EdDSA/Ed25519 验签作为旧密钥兼容能力,但不得把 EdDSA 当作当前主算法。
4. CRM 后端代码改造
Section titled “4. CRM 后端代码改造”4.1 新增四个公开认证路由
Section titled “4.1 新增四个公开认证路由”这些路由不能放在 auth:sanctum 中间件后面:
Route::prefix('auth/iam')->group(function () { Route::get('start', [IamAuthController::class, 'start']); Route::get('callback', [IamAuthController::class, 'callback']); Route::post('exchange', [IamAuthController::class, 'exchange']); Route::post('backchannel-logout', [IamAuthController::class, 'backchannelLogout']);});| 方法与路径 | 用途 |
|---|---|
GET /api/auth/iam/start |
创建 state、nonce 和 PKCE,跳转 IAM |
GET /api/auth/iam/callback |
IAM 回调;交换 Token、校验身份、绑定用户、签发一次性 ticket |
POST /api/auth/iam/exchange |
一次性 ticket 换 CRM Sanctum Token |
POST /api/auth/iam/backchannel-logout |
验证 IAM Logout Token,按 sid 撤销 CRM Token |
4.2 start 行为
Section titled “4.2 start 行为”请求:
GET /api/auth/iam/start?return_to=/dashboard后端必须:
- 检查 IAM 配置完整。
- 校验
return_to只能是 CRM 站内路径:以/开头、不能以//开头、不能包含 scheme、反斜杠或用户信息形式。 - 使用加密安全随机数生成
state、nonce、code_verifier。 - 计算
code_challenge = BASE64URL(SHA256(code_verifier))。 - 以 state 为键,将 verifier、nonce、return_to 写入 Laravel Cache,默认 300 秒。
- 整页 302 跳转到 IAM Authorization Endpoint。
Authorization 请求必须包含:
client_idredirect_uriresponse_type=codescope=openid profile emailstatenoncecode_challengecode_challenge_method=S2564.3 callback 行为
Section titled “4.3 callback 行为”后端必须:
- 拒绝或安全处理 IAM 返回的
error。 - 校验
code和state存在。 - 使用
Cache::pull一次性消费 state 上下文,拒绝过期或重放。 - 在 CRM API 服务端调用 Token Endpoint,使用
client_secret_basic和原始code_verifier。 - 要求 Token 响应同时包含
id_token和access_token。 - 通过 IAM Discovery/JWKS 验证 ID Token。
- 要求 ID Token 包含非空
sid。 - 使用 Access Token 调用 UserInfo,并校验 UserInfo
sub与 ID Tokensub一致。 - 向 IAM
POST /api/internal/provisioning/snapshot拉取当前开通快照(正文为{"clientId":"…","subject":"…"},携带 CRM HMAC 签名头),再经统一账户应用逻辑建号或同步;角色和准入以快照为准。签名头、规范串与错误码见账户与访问权限同步协议。 - 再次校验 CRM 本地
status为启用状态;本地手工禁用不被开通操作改回启用。 - 创建 32 字节随机、默认 60 秒有效、仅可使用一次的 ticket。
- 302 跳转到 CRM Web 的
/iam/callback?ticket=...。
URL 中只能携带随机 ticket,不能携带 IAM Access Token、ID Token、CRM Token 或用户资料。
4.4 exchange 行为
Section titled “4.4 exchange 行为”请求:
POST /api/auth/iam/exchangeContent-Type: application/json
{"ticket":"<one-time-ticket>"}后端必须:
- 使用
Cache::pull一次性消费 ticket。 - 区分“已使用”和“不存在/已过期”,两者都拒绝登录。
- 再次查询 CRM 用户并检查
status。 - 复用原 CRM 登录响应结构和本地角色逻辑。
- 创建 CRM Sanctum Token,并把 ticket 中的 IAM
sid写入personal_access_tokens.iam_session_id。 - 只把 CRM Sanctum Token 返回给前端。
成功响应应与旧 /api/auth/login 的前端消费方式兼容,至少包含:
{ "code": 200, "data": { "access_token": "<crm-sanctum-token>", "token_type": "Bearer", "user": { "id": 1, "name": "张三", "email": "zhangsan@example.com" }, "role": ["sales"], "team_leader": 0 }}4.5 Back-Channel Logout
Section titled “4.5 Back-Channel Logout”IAM 注销会向 CRM API POST:
POST /api/auth/iam/backchannel-logoutContent-Type: application/x-www-form-urlencoded
logout_token=<signed-jwt>CRM 必须验证:
- Header
typ === logout+jwt; - 签名和
kid; iss、aud、iat、exp;- 非空
jti、sid、sub; events包含http://schemas.openid.net/event/backchannel-logout;- 不能包含
nonce。
验证通过后:
- 以
jti建立短期防重放缓存。 - 删除
personal_access_tokens.iam_session_id = sid的 CRM Token。 - 重复 Logout Token 返回成功但不重复处理。
- 日志只记录必要的 sid/sub 和撤销数量,不记录完整 Logout Token。
4.6 ID Token 验证要求
Section titled “4.6 ID Token 验证要求”不能只 Base64 解码 JWT Payload。必须完成:
| 项目 | 规则 |
|---|---|
| 算法 | 当前必须支持 RS256;如保留 EdDSA,只能作为明确白名单兼容 |
kid |
必须在 IAM JWKS 中命中;未命中时清理 JWKS 缓存并允许后续重新拉取 |
iss |
严格等于 IAM_OIDC_ISSUER |
aud |
字符串或数组中必须包含 CRM Client ID |
azp |
存在时必须等于 CRM Client ID |
exp |
必须存在且未过期,可允许最多 60 秒时钟偏差 |
iat |
必须存在,不能显著晚于当前时间 |
nonce |
必须严格等于 start 阶段缓存值 |
sub |
必须是非空字符串 |
sid |
当前 CRM 方案要求存在,用于统一下线 |
必须拒绝 none、HS256 和其他未配置算法,不能根据 Token Header 动态放宽白名单。
4.7 运行依赖和缓存
Section titled “4.7 运行依赖和缓存”- 保留
ext-sodium,用于兼容旧 EdDSA/Ed25519 JWK;当前 RS256 使用 OpenSSL。 - Discovery 和 JWKS 可分别缓存 3600 秒。
- state、PKCE、ticket 和 logout
jti必须写共享缓存。 - CRM API 多实例生产环境必须使用 Redis 等共享 Cache Driver,不能使用每实例独立 file cache。
- 所有实例必须进行时间同步。
5. CRM MySQL 数据库改造
Section titled “5. CRM MySQL 数据库改造”CRM 数据库由 CRM 后端同事负责修改。IAM 负责人不执行以下 SQL 或 migration。
5.1 users 表新增字段
Section titled “5.1 users 表新增字段”| 字段 | Laravel 定义 | 约束 | 用途 |
|---|---|---|---|
iam_subject |
string nullable |
unique | IAM sub,绑定后的稳定身份主键 |
iam_linked_at |
timestamp nullable |
首次绑定时间 | |
iam_last_login_at |
timestamp nullable |
最近一次 IAM 登录时间 |
目标 migration:
Schema::table('users', function (Blueprint $table) { $table->string('iam_subject')->nullable()->unique()->after('wechat_work_userid'); $table->timestamp('iam_linked_at')->nullable()->after('iam_subject'); $table->timestamp('iam_last_login_at')->nullable()->after('iam_linked_at');});User Model 同步修改:
$fillable增加iam_subject、iam_linked_at、iam_last_login_at;$casts将两个时间字段设为datetime。
5.2 personal_access_tokens 新增字段
Section titled “5.2 personal_access_tokens 新增字段”| 字段 | Laravel 定义 | 约束 | 用途 |
|---|---|---|---|
iam_session_id |
string(191) nullable |
index | 保存 IAM ID Token 的 sid,用于精确撤销 CRM Token |
目标 migration:
Schema::table('personal_access_tokens', function (Blueprint $table) { $table->string('iam_session_id', 191)->nullable()->index();});不要给 iam_session_id 加 unique:同一 IAM Session 可以对应多个 CRM Token。
5.3 迁移前数据检查
Section titled “5.3 迁移前数据检查”新增字段本身不需要批量回填,但必须先检查旧 CRM 用户数据是否会导致首次绑定歧义:
-- 企业微信 UserID 重复;必须先人工确认SELECT wechat_work_userid, COUNT(*) AS cntFROM usersWHERE wechat_work_userid IS NOT NULL AND wechat_work_userid <> '' AND deleted_at IS NULLGROUP BY wechat_work_useridHAVING COUNT(*) > 1;
-- 邮箱忽略大小写后重复;开启邮箱首次绑定前必须处理SELECT LOWER(email) AS normalized_email, COUNT(*) AS cntFROM usersWHERE email IS NOT NULL AND email <> '' AND deleted_at IS NULLGROUP BY LOWER(email)HAVING COUNT(*) > 1;
-- 用户状态分布,确认旧数据中的启用值SELECT status, COUNT(*) AS cntFROM usersWHERE deleted_at IS NULLGROUP BY status;如果存在重复,不能依赖 Eloquent first() 随机绑定,必须在开放 IAM 登录前清理或人工确定归属。
5.4 执行 migration
Section titled “5.4 执行 migration”后端同事先备份 CRM MySQL,再执行:
php artisan migrate --pretendphp artisan migrate执行后检查:
SHOW COLUMNS FROM users LIKE 'iam_%';SHOW INDEX FROM users WHERE Column_name = 'iam_subject';SHOW COLUMNS FROM personal_access_tokens LIKE 'iam_session_id';SHOW INDEX FROM personal_access_tokens WHERE Column_name = 'iam_session_id';验收条件:
users.iam_subject可空且有唯一索引;- 两个 IAM 时间字段可空;
personal_access_tokens.iam_session_id可空且有普通索引;- 旧用户、旧 Sanctum Token、角色、团队和业务数据未被删除或重写。
6. CRM 用户匹配与绑定规则
Section titled “6. CRM 用户匹配与绑定规则”后端按以下顺序匹配,命中后停止:
users.iam_subject === sub;users.wechat_work_userid === wecom_user_id,且iam_subject IS NULL;- 仅迁移期
IAM_ALLOW_EMAIL_LINK=true时,使用 IAM 已验证邮箱匹配iam_subject IS NULL的 CRM 用户。
邮箱绑定必须同时满足:
email_verified或兼容字段emailVerified严格为布尔值true;- 邮箱格式合法;
- 不是
@wecom.invalid或@wecom.com占位邮箱; - CRM 邮箱忽略大小写后完全一致;
- CRM 用户尚未绑定其他
iam_subject。
首次匹配成功后写入:
iam_subject = IAM subiam_linked_at = 当前时间iam_last_login_at = 当前时间后续登录只按 iam_subject 匹配;邮箱或企业微信资料变化不应把账号漂移到另一个 CRM 用户。
当前范围明确不做:
- 不自动分配团队、客户、订单或其他业务权限;
- 不使用 IAM
app_role分配 Spatie 角色;由 CRM 部门 / 职位规则决定; - 不覆盖已有 CRM 账户的角色、团队和客户归属;
- 不允许未验证邮箱或占位邮箱绑定;
- 不允许一个 CRM 用户重新绑定另一个
sub; - 不删除旧账号密码登录和旧企业微信登录,是否下线由后续切换计划决定。
全员完成首次绑定并核对无误后,建议将 IAM_ALLOW_EMAIL_LINK 改为 false,只保留 iam_subject 和企业微信 UserID 的确定性匹配。
7. CRM API 环境变量
Section titled “7. CRM API 环境变量”CRM 后端需要配置:
IAM_OIDC_ISSUER=https://iam.cq-i.cn/api/authIAM_OIDC_CLIENT_ID=<由 IAM 提供>IAM_OIDC_CLIENT_SECRET=<由 IAM 安全提供>
# 必须与 IAM Client 登记值完全一致IAM_OIDC_REDIRECT_URI=https://<crm-api-domain>/api/auth/iam/callback
# CRM Web 公开回调页和登录页IAM_FRONTEND_CALLBACK=https://<crm-web-domain>/iam/callbackIAM_POST_LOGOUT_REDIRECT_URI=https://<crm-web-domain>/login
IAM_OIDC_SCOPES="openid profile email"IAM_OIDC_TOKEN_ENDPOINT_AUTH_METHOD=client_secret_basicIAM_OIDC_STATE_TTL=300IAM_OIDC_TICKET_TTL=60IAM_ALLOW_EMAIL_LINK=trueIAM_OIDC_HTTP_TIMEOUT=15IAM_OIDC_DISCOVERY_CACHE_TTL=3600IAM_OIDC_JWKS_CACHE_TTL=3600
# 登录兜底快照与角色映射(独立开关)IAM_SNAPSHOT_FALLBACK_ENABLED=trueIAM_SNAPSHOT_URL=IAM_ROLE_SYNC_ENABLED=true# 部门 / 职位规则在 CRM 管理页配置,不再使用 IAM_ROLE_MAPPING_JSONIAM_PROVISIONING_HMAC_KEY_ID=crmIAM_PROVISIONING_HMAC_SECRET=
# CRM 本地 Sanctum Token 过期时间,单位为分钟SANCTUM_TOKEN_EXPIRATION=720修改环境变量后执行:
php artisan optimize:clearIAM_PROVISIONING_HMAC_KEY_ID / IAM_PROVISIONING_HMAC_SECRET 用于快照查询的请求签名,签名头、规范串与错误码以账户与访问权限同步协议为准;两个值必须与 IAM 应用详情页一次性生成的凭据一致。
生产环境通常不需要手工配置 authorize/token/userinfo/JWKS 地址,优先从 Discovery 获取。仅联调或故障隔离时使用显式 endpoint override。
8. CRM Web 与后端的接口约定
Section titled “8. CRM Web 与后端的接口约定”本节用于后端联调,不要求 CRM 后端同事修改 Vue 代码。
CRM Web 发起登录必须使用整页跳转,不能用 Axios 请求 start 后期待自动完成跨站 302:
const url = new URL(`${CRM_API_URL}/auth/iam/start`)url.searchParams.set('return_to', '/dashboard')window.location.assign(url.toString())CRM Web /iam/callback 页面应:
- 读取
ticket; - 立即使用
history.replaceState清除地址栏中的 ticket; - POST
/api/auth/iam/exchange; - 按旧账号密码登录方式保存 CRM Sanctum Token;
- 恢复登录前的安全站内路径;
- 展示
error或iam_error,但不记录 ticket。
ticket 不能写入 localStorage、埋点、日志或错误上报。CRM Web 当前仓库尚未实现 IAM 回调页面和 IAM API 封装,因此后端上线前必须与前端负责人确认联调版本。
9. 发布与联调顺序
Section titled “9. 发布与联调顺序”后续发布顺序:
- CRM 数据库兼容迁移(含
iam_sync_states存量 revision 基线)。 - IAM 快照接口与开通投递修复(revision 原子更新、任务领取/完成匹配)。
- CRM API(统一账户应用、角色映射、登录快照兜底)。
- CRM 前端(待配置页、
access_state、错误展示)。 - 在 CRM 管理页配置部门 / 职位规则并对账。
- 启用快照兜底与角色同步开关。
- 用专门测试员工验收入职、调岗、撤权;监测失败、冲突、过期 processing 和待配置角色数量。
生产迁移、映射配置和部署另行执行。本次不提交、不部署。
10. CRM 后端目标文件清单
Section titled “10. CRM 后端目标文件清单”当前本地参考实现涉及:
| 文件 | 后端同事需要完成的内容 |
|---|---|
app/Http/Controllers/IamAuthController.php |
start、callback、exchange、backchannel logout |
app/Repositories/IamOidcRepository.php |
Discovery、PKCE、Token、UserInfo、JWKS、ID/Logout Token 验证 |
app/Services/IamUserMapper.php |
IAM Claims 到 CRM 用户的匹配与首次绑定 |
app/Models/User.php |
新增 IAM 字段 fillable/casts |
routes/api.php |
注册四个公开 IAM 路由 |
config/services.php |
IAM OIDC 配置 |
config/sanctum.php |
CRM Token 过期时间 |
.env.example |
IAM 配置项说明,不包含真实 Secret |
composer.json |
声明 ext-sodium 兼容依赖 |
database/migrations/2026_08_26_000001_add_iam_fields_to_users_table.php |
修改 users |
database/migrations/2026_08_28_000002_add_iam_session_id_to_personal_access_tokens_table.php |
修改 personal_access_tokens |
11. 测试和验收
Section titled “11. 测试和验收”11.1 后端自动化测试
Section titled “11.1 后端自动化测试”至少执行:
php artisan test \ tests/Feature/IamAuthFeatureTest.php \ tests/Unit/IamAuthTokenResponseTest.php \ tests/Unit/IamOidcIdTokenTest.php \ tests/Unit/IamOidcPkceTest.php \ tests/Unit/IamReturnToValidationTest.php \ tests/Unit/IamUserMapperTest.php必须覆盖:
- PKCE S256、state、nonce 和 Authorization URL;
- ID Token RS256 验签及错误 iss/aud/azp/exp/iat/nonce;
- Logout Token typ/events/jti/sid/sub 和重放防护;
- ticket 过期和重复消费;
iam_subject、企微 UserID、已验证邮箱三种匹配路径;- 占位邮箱、未验证邮箱、禁用用户和不存在用户;
return_toOpen Redirect 防护;- CRM Token 正确写入
iam_session_id; - Back-Channel Logout 只撤销对应 sid 的 CRM Token。
11.2 联调检查
Section titled “11.2 联调检查”- IAM Discovery 的 issuer 与
IAM_OIDC_ISSUER完全一致。 - IAM Client Redirect URI 与 CRM 配置逐字符一致。
- CRM Client 为机密客户端、
client_secret_basic、Authorization Code + PKCE。 - CRM API 能访问 IAM Discovery、JWKS、Token 和 UserInfo。
- CRM Web 不包含 Client Secret。
- 首次登录后 MySQL 正确写入
users.iam_subject和时间字段。 - 再次登录只按
iam_subject成功,不依赖邮箱继续一致。 - CRM 返回的是本地 Sanctum Token,本地角色和团队权限未改变。
- 同一 ticket 第二次提交被拒绝。
- CRM 停用用户不能通过 IAM 登录绕过本地状态。
- IAM 注销后,对应
iam_session_id的 CRM Token 被删除,其他会话不受影响。 - 日志中没有 Client Secret、Authorization Code、ID Token、Access Token、ticket 或完整 Claims。
12. 上线后的安全收口
Section titled “12. 上线后的安全收口”- 全员绑定完成后设置
IAM_ALLOW_EMAIL_LINK=false。 - 定期清理过期 Sanctum Token;不能只依赖请求时的 expiration 判断。
- 监控 OIDC callback 失败率、state/ticket 过期、JWKS 拉取失败和用户绑定失败。
- Client Secret 泄露时,由 IAM 负责人重置 Secret,CRM 后端同步更新并清理配置缓存。
- 用户解绑或换绑必须走管理员审批和审计流程,不允许直接通过登录自动覆盖
iam_subject。 - 旧账号密码和旧企微登录的下线应单独制定灰度计划,本次接入不自动删除旧能力。
13. 双方最终交付物
Section titled “13. 双方最终交付物”IAM 负责人
Section titled “IAM 负责人”- CRM Client ID 和通过安全渠道交付的 Client Secret;
- issuer、Redirect URI、Back-Channel Logout URI 配置截图或脱敏记录;
- 测试用户在 IAM 的
sub、wecom_user_id和邮箱验证状态核对结果。
CRM 后端同事
Section titled “CRM 后端同事”- 实际后端 commit/分支;
- 两条 Laravel migration 及执行结果;
- 第 5.3 节重复数据检查结果;
- 自动化测试结果;
- 登录和 Back-Channel Logout 联调记录;
- 未完成项、兼容项和旧登录下线计划。
14. 参考
Section titled “14. 参考”- Better Auth OAuth 2.1 Provider
- IAM OAuth Provider 项目文档
- 对接方式总览
- 账户与访问权限同步协议:HMAC 签名头、规范串、事件负载与快照接口
- IAM 当前实现:
packages/auth/src/plugins.ts、packages/auth/src/jwt-plugin-config.ts - CRM 当前参考实现:
crm-api/app/Http/Controllers/IamAuthController.php、crm-api/app/Repositories/IamOidcRepository.php