You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

10 KiB

认证与第三方登录(crm-auth)

围绕三方扫码登录、本地用户定位与身份绑定的领域语言。

Language

孤儿身份: 三方身份绑定(identity)仍存在,但其指向的本地用户已被物理删除的状态。持有孤儿身份的人再次登录时,按手机号匹配或首登注册重新定位用户,并把原绑定重定向到新用户,避免下次登录再次命中孤儿。 Avoid: 脏数据、失效绑定

主部门: 一个用户唯一的正式归属部门,代表其组织身份与汇报线,用户信息展示、组织架构归位均以主部门为准。每个用户至多一个主部门。 Avoid: 归属部门、默认部门

兼职部门: 用户除主部门之外额外挂靠的部门,代表其参与该部门业务但组织身份不在其中。一个用户可有多个兼职部门,也可以没有。 Avoid: 副部门、附属部门

只读挂靠: 兼职部门当前的权限语义:用户能读到兼职部门范围内的数据,但其自身产出的业务数据恒归属主部门,不进兼职部门。因此兼职部门的管理者看不到该成员在本部门产出的数据。 Avoid: 双向兼职、完全平等

部门集合: 一个用户的主部门与全部兼职部门的并集,数据范围计算以该集合为输入,集合内各部门地位平等。 Avoid: 权限部门、可见部门

名称回显: 把已保存的部门 ID / 用户 ID 换成中文名称在展示层显示。批量入口是 ISysDeptService#getDeptNames(deptIds)id → deptNameIAuthUserService#getUserNames(userIds)id → username,供 crm-lead / crm-opportunity / crm-rule 等调用方回填 DTO;「填哪些字段」留在各调用方,认证侧只负责「ID → 名称」这一段(与 crm-dict 字典名回显同型)。 Avoid: 名称翻译、label 回填

一律不可见: 数据范围档位要求按部门过滤,但该用户的部门集合为空时的判定结果:按部门归属的数据一行都不可见。无归属不等于全放行,此时查询注入永假条件而非放弃过滤。它是权限判定的结论,不是查询恰好没有命中数据。 Avoid: 空结果、查无数据、不过滤

在职状态: 用户的雇佣状态:在职 / 离职。来源于 OA 系统,CRM 不产生、只消费;离职用户仍保留在用户列表中(名下可能仍挂有业务数据),而非删除。与“账号是否可登录”是两回事——离职账号应禁止登录,被禁用的在职账号仍是在职。 Avoid: 员工状态、在职/离职员、启用状态

用户: 本系统中来自 OA 的人员统一称“用户”,不使用“员工/用工”。用户基本信息(姓名/部门/职务/手机号/在职状态)唯一来源为 OA,CRM 不产生、只消费。 Avoid: 员工、用工、人员

待分配用户: 在职且未持有任何角色的用户。该指标提醒管理员“有新人尚未配权限”;离职用户不计入,无论其是否持有角色。 Avoid: 无角色用户、未授权用户

免登: 用户在浏览器侧已登录钉钉后访问 CRM 时,前端重定向到钉钉 OAuth 授权页,钉钉检测到已登录态后自动重定向回 CRM 并携带 authCode,前端将 authCode POST 到现有扫码登录端点完成登录。免登与扫码登录在后端走同一套 OAuth2 userAccessToken 换取逻辑,区别仅在前端获取 authCode 的方式(重定向 vs 扫码),后端无感知。 Avoid: 自动登录、SSO、静默登录

端类型: 单端在线唯一性判定的维度,取值 pc / mobile(枚举可扩展,未来可加 miniapp 等)。作为登录入参 clientType 传入(大小写不敏感,不传默认 pc,老前端兼容);只存 Redis 侧(会话 JSON 与反向索引 key 段),JWT payload 不带。检测与顶号同端:PC 登录只查/只踢 PC 会话,不碰移动端。 Avoid: 设备类型、终端、设备指纹(明确不做物理设备指纹)

单端在线: 核心不变量:任意时刻每个 (userId, clientType) 至多一个活会话。一次登录态 = 一个有效 token:{jti} Redis 键;反向索引 session:{userId}:{clientType} → jti 只是线索,任何判定都回查 token 键确认活着才算在线(脏索引无害,命中顺手删)。索引与 token 键 TTL 对齐并跟随滑动续期。 Avoid: 单点登录、互踢、同账号互斥

顶号: 同一用户在新设备登录同一端类型时,把该端已有在线会话踢下线并接管登录态的动作。交互为两接口:主登录检测到设备在线时不签正式 token,返回 61013 + 短命 preLoginToken;用户确认后凭 preLoginToken 调 confirm 接口完成踢旧签新。已知取舍:confirm 的「踢旧」与「签新覆写索引」之间存在毫秒级窗口,若同端主登录恰好插入,可产生一个不被索引追踪的孤儿活会话(最多 1 个,靠自然过期自愈)——同端并发竞态的专项并发控制已明确排除在范围外。 Avoid: 强制下线(管理员按用户/端批量踢会话是另一回事,已明确排除)、挤下线

预登录票: 短命一次性凭证 preLoginToken(UUID,TTL 默认 2 分钟可配),仅代表「已认证、待确认顶号」。存于 prelogin:{token} → {userId, clientType},confirm 接口 GETDEL 原子消费——拿到即烧,防并发双签/重放;过期/已消费/伪造统一报 61014,用户重走主登录。 Avoid: 临时 token、授权码(与钉钉 authCode 是两回事)

被顶下线: 旧设备会话被顶号踢除后的识别状态:踢除时埋标记 kicked:{旧jti}="1"(TTL 对齐原会话有效期),旧设备下次请求 token 校验失败时查该标记命中,返回专门的 61015 错误码(HTTP 仍 401),前端据此弹「已在其他设备登录」而非「登录过期」。标记留到 TTL 自然过期,不读后即焚。 Avoid: 踢下线、强制退出、账号冲突

组织准入: 登录流程中对新用户的组织归属校验:当用户在本地 DB 中查不到时,用企业级 access token 调钉钉组织通讯录 API(按 unionId 查询),确认该用户属于本企业后才允许自动注册。不属于组织的用户拒绝登录并返回「不属于该组织」错误。已在 DB 中的用户不受此校验影响(登录时不重复校验)。 Avoid: 组织校验、企业校验、准入检查

企业级 token: 用 clientId + clientSecret 向钉钉换取的组织级 access token,用于调用组织通讯录等需要企业身份的 API。与登录流程中的用户级 token(userAccessToken)不同:企业级 token 代表应用/组织身份,用户级 token 代表个人身份。缓存于 Redis,TTL 略低于钉钉颁发的 7200 秒有效期。由 CorpAccessTokenProviderservice/client)统一提供:get() 读缓存/未命中换取回写,forceRefresh() 弃旧取新(调用方收到 40014/42001 失效时调用);登录组织校验与组织同步共用这一模块,缓存 key 不再被调用方直接触碰。 Avoid: corpToken、应用 token、组织 token

权限资源树: RBAC 的统一可授权资源模型:菜单分组(catalog)、菜单页面(menu)、按钮/权限点(button)三类节点在同一张 sys_menu 表中扁平存储,通过 parentId 组成树。catalog 是收纳容器不承载页面,menu 是可导航的页面,button 是挂在其下的叶子权限点。三层固定层级:根→catalog/menu→menu→button,分组不嵌套、按钮必挂页面下。 Avoid: 菜单表、资源表、权限树

权限码并集: 用户登录后,其所有角色授权的按钮节点(type=button 且 status=enabled)的 perms 字符串收集而成的集合,与 roleCode 一起注入 Spring Security authority,使 @PreAuthorize(hasAuthority('crm:xxx:yyy')) 生效。停用的权限点不进并集(全局断路),无需逐角色取消授权。 Avoid: 权限集合、按钮权限列表

权限点停用: button 节点的 status=disabled 状态:即使已被角色授予,权限解析引擎也不将其 perms 纳入权限码并集——相当于全局断路。重新启用后原授权自动恢复,无需管理员重新勾选。停用/启用只改 status 字段,不涉及角色授权关系变动。 Avoid: 权限删除、授权撤销

角色授权集合: 一个角色被授予的全部资源节点 ID,存储于 sys_role_menu 表。保存时由后端沿 parentId 向上遍历补全祖先 catalog/menu 节点(ADR-0005),因此存储态始终是一棵完整子树——用户勾选了某 button,其所属的 menu 和 catalog 一定也在集合中。角色详情接口返回此完整集合,前端直接用于渲染勾选状态,无需二次补全。API 层称 resourceIds,存储层为 sys_role_menu.menu_id。 Avoid: 菜单ID列表、menuIds(API 层术语)、勾选项

祖先补全: 角色权限保存时的后端操作:对前端传入的每个勾选节点 ID,沿 parentId 递归向上收集祖先 catalog/menu ID,合并入集合后再入库。保证菜单可见性链路完整——若只存叶子 button 而不补全其祖先 menu/catalog,权限解析引擎建菜单树时祖先不在授权集合中,用户虽有按钮权限却看不到所在页面。补全发生在存储时而非读取时,读取返回的就是已补全的完整集合。 Avoid: 前端补全、读取时补全

内置角色: SysRole 表中 builtin=true 的角色,代表系统运行所必需、不可删除、不可改 roleCode 的角色。ADMIN 是典型内置角色——若被删除,持有它的用户瞬间失去全部管理权限,系统不可恢复。builtin 字段由数据初始化器设置,创建角色接口不允许设 builtin=true。其他字段(roleName、dataScope、sort、remark)可正常编辑。 Avoid: 系统角色、固定角色、超级管理员

数据权限模块: 数据权限按业务模块分别配置时的「业务模块」概念(线索/商机/客户/项目,可扩展),存在于 sys_data_scope_module 注册表中,用 code 标识(稳定标识,不引用 id)。一个角色对每个模块各有一档数据范围档位,多角色对每个模块各自取最宽(ADR-0008 的规则下沉到每模块)。模块列表数据驱动(管理员可 CRUD),受控实体通过 @DataScope(module = "...") 注解声明所属模块,启动时与注册表交叉校验,未注册的 module code 拒绝启动(ADR-0006)。 Avoid: 业务域、功能模块、权限域