平台认证与鉴权流程
本文依据当前工程中的实际实现整理,重点覆盖:
sunri-service-auth-spi:认证、Token、权限和资源的 Service Bus 接口定义。sunri-service-auth:账号校验、JWT 签发、实时库会话、角色权限位和资源关系实现。sunri-web-center:HTTP 登录入口、请求拦截器、@ApiRight接口鉴权和菜单过滤。
本文描述的是平台现状,不把它替换成 Spring Security/RBAC 的标准实现。
1. 先看结论
平台的核心授权链不是“用户 -> 角色 -> 权限记录 -> 接口”这种普通关联表模型,而是:
用户 -> 单个角色 -> 角色的 64 位权限位图 -> 权限位集合
|
+-> 菜单所需权限位
+-> API 所需权限位登录成功后,角色的 ROLEAUTHORITY 位图会被拆成 Set<Integer>。这个权限集合会同时:
- 返回给 Web Center;
- 写入实时库的登录会话;
- 首次校验受保护请求时缓存在 Web Center 本地;
- 用于过滤菜单;
- 用于 API 请求鉴权。
2. 核心关系图
2.1 主干关系
| 起点 | 终点 | 实际字段/规则 | 说明 |
|---|---|---|---|
用户 userinfo | 角色 userroleinfo | ROLESOFUSER = ROLEID | 当前实现是一个用户一个角色,不是用户角色多对多。 |
| 角色 | 权限位 | ROLEAUTHORITY | 使用 Long 位图保存角色拥有的权限。 |
| 权限位定义 | 权限类型 | cfgauthconst.authtypeid | 权限类型用于分类、角色组适配和互斥校验。 |
| 菜单/API | 权限位 | cfgresourceright(resourceId, right) | 一个资源可以关联多个权限位。 |
| 菜单/API | 统一资源表 | cfgauthresource.type | 菜单和 API 共用一张资源表。 |
| 菜单 | 前端组件 | componentid | 组件配置补充菜单的组件类型和展示配置。 |
2.2 权限位如何保存
假设角色拥有权限位 {0, 2, 5},其位图概念上是:
二进制: 100101
十进制: 37数据库在 userroleinfo.ROLEAUTHORITY 中保存合并后的 Long;登录时通过 RightBitUtils.extract() 拆成整数集合。cfgauthconst 保存每个权限位的名称和分类信息,而不是给每个角色保存一条关联记录。
特殊权限使用 -1。项目中大量管理接口使用 @ApiRight(value = -1),表示需要特殊/超级权限。
3. 登录认证流程
浏览器登录入口是 Web Center 的 POST /api/doLogin,Web Center 再通过 Service Bus 调用 AuthorityVerifyService.loginVerify()。
3.1 auth 服务登录管线
LoginVerify 的主处理顺序为:
UniqueLoginHandler(条件启用)
-> AuthAccessLoginHandler
-> APPLoginHandler
-> BrowserLoginHandler
-> HMILoginHandler浏览器登录内部还会按顺序执行多个 BrowserLoginPipeline,主要完成:
- 查询用户是否存在;
- 从用户角色读取权限位图并拆出权限集合;
- 检查账号状态、锁定状态、账号有效期和允许访问时间;
- 检查临时密码、普通密码、初始密码和密码有效期;
- 检查最大会话数和并发登录策略;
- 根据用户或系统配置切换 UKey、指纹等高级认证模式。
任一步设置 errorStr 后,后续成功逻辑不会签发有效 Token。
3.2 登录访问规则
cfgauthaccess 不属于角色权限,但属于完整认证关系:
规则解释器支持日、月、星期、小时、时间段、IPv4 和 MAC 地址等条件。
4. 登录会话与 Token
JWT 是客户端凭证,实时库记录才是平台在线会话。会话主要包含:
| 字段 | 用途 |
|---|---|
username | 当前登录用户。 |
token | 当前会话 JWT。 |
right | 登录时得到的权限位集合 JSON。 |
clientIp | 当前来源或同源 IP 集合。 |
loginLimit | 并发登录策略。 |
clientNum | 同一会话下客户端数量。 |
interrupt | HMI 登录中断标记。 |
| TTL | 剩余在线时间。 |
单会话通常按以下键保存:
{pcname}:{paname}:token:{username}允许多源同时在线时,使用 Token 的 MD5 作为会话键,并维护用户到 Token 哈希的集合:
{pcname}:{paname}:token:{md5(token)}
{pcname}:{paname}:bucket:{username}expandOnlineDuration() 只延长实时库 TTL,且不超过平台配置的最大时长;它不会重新签发 JWT。
5. 普通请求的 Token 校验
Web Center 使用自己的 Spring MVC 拦截器体系,不使用 Spring Security 的 SecurityContext。
@PassToken 用于无需登录的接口,例如登录和验证码。@ApiRight(ctrl = false) 表示接口不做权限位校验,但不等同于 @PassToken。
6. API 接口鉴权
API 权限不是 @PreAuthorize,而是平台的 @ApiRight、API资源表和 Web Center 校验管线共同完成。
6.1 API 资源初始化
应用启动时,DefaultAPIAuthorityManager 扫描 Controller 方法:
- 跳过带
@PassToken的公共接口; - 读取请求路径、HTTP 方法和 Java 方法引用;
- 读取
@ApiRight的默认权限位和ctrl; - 通过
AuthorityResourcesService.updateAPI()同步到cfgauthresource(type = 2); - 从
cfgresourceright加载管理员配置后的最终权限位; - 建立“Java 方法引用 -> API 所需权限集合”的内存映射。
6.2 运行时校验
系统支持两种执行模式:
- 缓存鉴权:
AuthorityCacheValidateHandle直接比较 Web Center 缓存的用户权限与 API 权限。 - 无缓存鉴权:
AuthorityRightCheckHandle调用AuthorityVerifyService.rightVerify(),由 auth 服务比较权限。
判断逻辑是“用户权限集合”和“API 所需权限集合”存在交集即可通过,并非必须拥有接口配置的全部权限位。
7. 菜单鉴权
菜单和 API 使用相同的资源-权限位映射,但菜单鉴权主要决定前端资源是否可见或可用。
需要注意:auth 服务的 queryMenuTree() 返回菜单及其所需权限;真正结合当前登录用户权限进行过滤的逻辑在 Web Center 的 AuthorityResourceHandler。
8. 登出与权限变更
8.1 登出
UserLogoutController -> LogoutHandler -> AuthorityVerifyService.loginOut():
- 单客户端会话:删除实时库 Token 会话;
- 多客户端共享会话:减少
clientNum; - 多源并存会话:同时清理用户的 Token 哈希集合;
- Web Center 清除本地
UserAuthority和待机心跳状态。
浏览器关闭、待机超时、管理员踢出和部分用户配置变更也会触发登出或重新登录通知。
8.2 权限变更何时生效
角色权限在登录时写入实时库和 Web Center 缓存,因此修改角色权限后,代码会通知受影响用户重新登录。重新登录后才会完整刷新:
角色 ROLEAUTHORITY
-> 登录时拆分 rights
-> 实时库会话 right
-> Web Center UserAuthority.userRights
-> 菜单/API 鉴权9. 容易漏掉的关系
除了“用户、角色、权限位、菜单、API”,项目中还存在以下认证相关关系:
| 关系 | 是否属于核心授权 | 作用 |
|---|---|---|
用户组 usergroup | 否 | 用户业务分类,通过 USERGROUPID 关联,不直接生成权限。 |
| 角色组 | 辅助 | 对角色分类,并限制可配置的权限类型。 |
权限类型 cfgauthtype | 辅助 | 对权限位分类,配置互斥关系和适用角色组。 |
登录访问规则 cfgauthaccess | 认证前置 | 按平台、IP、MAC、日期和时间执行黑白名单。 |
| 用户系统配置 | 认证策略 | 控制密码复杂度、失败锁定、账号有效期、并发登录和待机退出。 |
菜单组件 cfgcomponent | 展示辅助 | 为菜单补充前端组件类型、样式和数据配置。 |
| 实时库 Token 会话 | 核心认证 | 保存在线状态、权限快照、来源 IP 和 TTL。 |
| Web Center 权限缓存 | 核心鉴权 | 减少每个请求调用 auth 服务的次数。 |
| 双机通知 | 运行保障 | 同步 Token 检查结果、踢出状态和重新登录通知。 |
| 超级用户 | 特殊分支 | 权限来源可由配置提供,管理菜单和接口常使用权限位 -1。 |
| CAS/统一登录 | 特殊认证 | 通过 genTokenFromCas()、unionLogin() 接入平台会话体系。 |
CfgRightMenuResource 实体目前没有对应 Mapper 或实际调用,不能作为当前生效关系理解;实际资源与权限位关系以 cfgresourceright 为准。
10. 当前实现边界
- auth SPI 是 Service Bus 接口;面向浏览器的 REST 接口位于 Web Center。
- 平台使用自定义 MVC 拦截链,不使用 Spring Security。
- 用户当前只关联一个角色;角色权限使用 64 位位图,权限位数量受该模型限制。
- 菜单权限解决前端可见性,API 权限负责服务端访问控制;不能只配置菜单权限而不配置 API 权限。
checkToken()在多源模式优先按 Token 哈希查会话,在其他情况下会回退按用户名查会话。Web Center 随后还会比较返回 Token 和请求 Token,但直接调用 auth SPI 时应理解这一现有行为。- 在线续期延长的是实时库会话 TTL,不是标准 OAuth2 Refresh Token 流程。
11. 关键代码入口
| 功能 | 代码入口 |
|---|---|
| auth SPI | AuthorityVerifyService、AuthorityResourcesService、UserRoleService |
| 登录实现 | AuthorityVerifyServiceImpl.loginVerify()、LoginVerify |
| 用户和角色权限加载 | QueryBrowserLoginUserExistence、RightBitUtils |
| Token 会话 | LoginVerify.writeTokenToRedis()、AuthorityVerifyServiceImpl.checkToken() |
| Web 登录入口 | UserLoginController、LoginAuthorityHandler |
| Web Token 拦截 | JwtInterceptorConfig、AssembleHandle |
| API 权限 | ApiRight、DefaultAPIAuthorityManager、AuthorityCacheValidateHandle、AuthorityRightCheckHandle |
| 菜单权限 | AuthorityResourcesServiceImpl、MenuResourcesHandler、AuthorityResourceHandler |
