Files
full/docs/mgt.md
2026-09-22 21:15:34 +08:00

15 KiB
Raw Blame History

mgt后台组织与权限管理代码审计报告

内容
审计对象 module/base/mgt
服务域 基础与平台服务
审计日期 2026-09-22
代码规模 手写 Go 文件约 80 个(cmd/internal/ 全量、service/types/middleware/libs/无 proto 定义(纯 Gin REST无生成代码
入口 cmd/mainGin 单进程)、聚合入口 pkgs/allpkgs/ecmall
对外协议 仅原生 REST基础前缀 /rest/mgtREADME 所述 /mgt/v1/... 与实际不符)
结论摘要 鉴权中间件已正确挂载于全部管理路由JWT + 超级管理员角色双重校验模块内无绕过SQL 全部参数化。但 LoginAppId/AppKey 路径以明文相等比对密码列哈希即凭证dev 配置启用 InitRootUser 注入 root/123456 弱默认账号;列表查询无分页上限;配置文件注释与实现行为矛盾。

1. 服务定位与职责

后台 RBAC 权限管理系统:用户、角色、权限(菜单/按钮、应用、部门的管理与关联。负责管理员账号生命周期、登录认证、Token 刷新、短信重置密码、以及权限/角色/部门树的维护。它是**管理面admin console**服务,所有写操作需超级管理员权限。

2. 代码结构与入口

路径 职责
cmd/main/main.go 进程入口配置→impl→Gin→session→中间件→routers.Register→监听
internal/config/config.go 配置结构(Databases/SecretKey/InitRootUser 等);设置 JWT 密钥来源
internal/impl/{impl,with}.go 初始化 Redis/DB/Etcd/内存缓存DB 连接mysql/postgres+ 自动迁移
internal/middleware/rbac.go RequireAdmin / IsSuperAdmin / EnsureSelfOrAdmin 管理员鉴定
internal/libs/validator.go 参数校验(手机号、邮箱自定义规则)
internal/routers/register.go REST 路由注册(匿名组 + 鉴权组 + 管理员组)
internal/logic/hello/ping.go 健康检查、Session 调试端点
internal/logic/pub/{login,refresh,forget}.go 公开接口:登录、刷新、短信重置
internal/logic/user/* 用户 CRUD、角色/权限分配、查询
internal/logic/role/*permission/*application/*department/* 各资源 CRUD 与关联
internal/logic/tool/tool.go 分页参数处理
internal/models/* 16+ 模型 + impl.go(迁移与 root 初始化) + init_db.go(root 数据) + init_menu.go(菜单)
internal/types/{vars,req,resp}.go 常量、请求/响应结构
service/{expose,dependencies}.go 聚合宿主注入(共用 Engine 与连接)

logs/passport 不同mgt 没有 proto/gRPC,是纯 Gin 服务;聚合入口 service.Expose 直接复用同一 Engine 注册路由,鉴权接线一致。

3. 接口清单

鉴权分三层:anonymous(无)、authmiddleware.JwtAuth)、adminmgtmw.RequireAdmin)。所有管理写操作均挂在 admin 组。

方法 路径 功能 鉴权 实现位置
GET /rest/mgt/ping 健康检查 logic/hello/ping.go
GET /rest/mgt/session Session 调试 logic/hello/ping.go
POST /rest/mgt/login 登录(账号密码 / AppId+AppKey logic/pub/login.go
POST /rest/mgt/refresh 刷新 Token auth logic/pub/refresh.go
POST /rest/mgt/reset 短信验证码重置密码 logic/pub/forget.go
POST /rest/mgt/user/role /app /pmn /pmn_tree 当前用户角色/应用/权限查询 auth logic/user/*
POST /rest/mgt/user/create /del /detail /modify /fetch /list /set_role /del_role /set_pmn /modify_pmn /del_pmn 用户管理(全部写操作) admin logic/user/*
POST /rest/mgt/app/* 应用管理 admin logic/application/*
POST /rest/mgt/role/* 角色管理 admin logic/role/*
POST /rest/mgt/pmn/* 权限管理 admin logic/permission/*
POST /rest/mgt/dpt/* 部门管理 admin logic/department/*

对照发现README 描述的路径前缀 /mgt/v1/... 与实际注册前缀 /rest/mgt/... 不一致;ForgetPwdByPwd(旧密码改密)为空 stub 且未注册路由

4. 数据模型与表

表名 字段 类型 键/约束 说明
mgt_user id - PK 用户
account varchar(255) uniqueIndex 账号(含 root
phone varchar(255) - 手机号
password varchar(255) - 密码bcrypt / 兼容 MD5 十六进制)
salt varchar(255) - 密码盐
last_login_at time - 最后登录
mgt_role id/name/description - - 角色(含 超级管理员
mgt_permission id/code/title/menu_path/type/app_id/parent_id - - 权限/菜单
mgt_application id/workspace/title - workspace 唯一 应用
mgt_department id/name/parent_id/leader_id - - 部门
关联表 mgt_link_user_role mgt_link_user_pmn mgt_link_user_app mgt_link_user_dpt mgt_link_role_pmn mgt_link_role_app mgt_link_dpt_role mgt_link_dpt_pmn - 复合唯一 多对多关联

5. 核心流程

flowchart TD
    A["POST /rest/mgt/login"] --> B["按 account 或 AppId 查 mgt_user"]
    B --> C{"AppKey 路径?"}
    C -->|否| D["checkPwdAndStatus: bcrypt / MD5 比对"]
    C -->|是| E["checkAppKeyAndStatus: 明文相等比对密码列"]
    D --> F["签发 JWT"]
    E --> F
    G["POST /rest/mgt/user/create"] --> H["RequireAdmin 校验超级管理员"]
    H --> I["bcrypt 哈希 + 事务建用户/关联"]
    J["POST /rest/mgt/reset"] --> K["校验短信验证码(Redis)"]
    K --> L["bcrypt 重置密码"]

6. 审计发现

6.1 安全

级别 位置 问题
internal/logic/pub/login.go:147-159 checkAppKeyAndStatus 以明文相等比对密码列if userPwd != pwd 直接比较存储的 password 字段与传入 AppKey。对 MD5 兼容老用户(密码列存 md5(pwd+salt) 十六进制),攻击者只要拿到密码哈希,以 app_id=账号app_key=哈希 即可登录,哈希本身成为可用凭证pass-the-hash,加密存储失去意义。
etc/mgt_dev.yaml:16 + internal/models/impl.go:69-70 + internal/models/init_db.go:16 弱默认管理员账号dev 配置 InitRootUser: true,启动时 InitRootUserData() 创建 root 账号、口令硬编码 123456。非生产环境一旦启用即注入后门级默认凭据;代码不强制改密。
internal/config/config.go:19,24-29 配置语义矛盾:注释"未配置时默认 true",但实现 InitRootUser==nil 时返回 falseREADME:245 又写"默认 false"。部署者若省略该字段,实际不会创建 root与注释和部分文档预期相反易误导安全预期、造成"无可用管理员"或反之。
internal/config/config.go:39 JWT 密钥来源不可见Spec.SecretKey = env.Runtime.JwtSecretKeyyaml 中的 SecretKey: CHANGE_ME 被覆盖忽略。真实密钥强度/是否强制设置取决于 bsm-sdk 与环境变量,本模块无法确认【信息不足】。
internal/logic/hello/ping.go:25-35 + internal/routers/register.go:32 /rest/mgt/session 为匿名调试端点:写入固定 session 并打印到 stdout属调试残留可被探测/泄露。
internal/logic/pub/login.go:108-116 SaveToken 将 token 写入 Redis 时 TTL=0永不过期该函数未被任何路径调用死代码但一旦启用即永久缓存 token。

管理员鉴定本身正确routers/register.go:42-43 在管理路由挂 RequireAdmin,其 IsSuperAdmininternal/middleware/rbac.go:19-36)按 id 回查 DB 确认账号为 root 或拥有 超级管理员 角色,不以 JWT 声明直接判定,模块内无明显绕过。唯一残留风险在于 SDK 的 ParseAuth 是否校验 JWT 签名(见 6.4)。

6.2 正确性与逻辑缺陷

级别 位置 问题
internal/logic/tool/tool.go:5-14 列表查询无分页上限GetSizeAndPagesize<=0 才取默认 10否则直接使用请求值。所有 *Fetch/list 接口user/role/app/pmn/dpt接受任意 size,可触发无上限批量查询(全表扫描 / DoS / 数据拖库)。
internal/config/config.go:24-29 InitRootUserEnabled 在 nil 时返回 false与同文件注释及 README 宣称的"默认 true"不一致(同 6.1 中)。
internal/models/impl.go:106,113 SQL Debug 全量开启mysql/postgres 连接均硬编码 Debug: true,所有 SQL含敏感查询进入日志生产环境应关闭。
internal/logic/user/create.go:57-72internal/logic/application/create.go:50-68 创建时对已软删的同 phone/account或 workspace/title记录执行 Unscoped().Delete 硬删除再重建,可能误删历史数据且不可恢复。

6.3 未完成实现

  • internal/logic/pub/forget.go:103-104ForgetPwdByPwd(凭旧密码改密)为空 stub且未注册路由。
  • internal/logic/pub/login.go:108SaveToken 死代码(无调用点)。
  • internal/models/init_menu.go:16 引用 etc/menu.json;本仓库 etc/ 下为 menu.json(已存在)但 scripts/other 提及 menu_structure.json,文件缺失会导致 InitRootUserData 事务因 Load 失败而回滚、root 创建失败【信息不足,需确认部署包含该文件】。
  • 全模块无 Go 单元测试覆盖逻辑层(仅 libs 校验;test/ 下多为 .http 手工用例)。

6.4 健壮性与可维护性

级别 位置 问题
internal/models/impl.go:106,113 SQL Debug 全量日志(同 6.2),泄露查询结构。
internal/logic/user/del.go:16-78 无"禁止删除 root / 超级管理员"保护:超级管理员可删除 root 或另一超级管理员导致管理面锁死DoS
internal/logic/pub/login.go:80user/create.go:46 等多处 大量 printer.Error/Info 打印账号、手机号、内部错误等敏感信息,存在信息泄露。
全模块 逻辑层无单元测试;config 注释与实现、README 与真实路由前缀多处不一致,增加维护与误配风险。
【信息不足】 git.apinb.com/bsm-sdk/core/middleware.ParseAuth 管理员判定的前提是 JWT 签名被 ParseAuth 正确校验;该函数在 bsm-sdk 内,本模块不可见,无法确认签名校验是否到位(若未校验,可伪造 id/account=root 的 token 越权)。建议核实 SDK 实现。

7. 风险汇总

编号 级别 问题 影响面
M1 AppKey 登录明文比对密码列(哈希即凭证) 密码哈希泄露即等于账号失陷
M2 dev 默认 root/123456 弱管理员账号 非生产环境被注入后门凭据
M3 InitRootUser 注释/实现/文档行为不一致 安全预期误导、root 可能未创建
M4 JWT 密钥来源不可见 密钥强度不确定
M5 列表查询无分页上限 全表扫描 / DoS / 拖库
M6 SQL Debug 全量日志 查询与数据泄露
M7 匿名 session 调试端点 探测/信息泄露
M8 可删除 root、无单测、信息打印 健壮性与可维护性
M9 【信息不足】 SDK ParseAuth 签名校验未知 管理员越权潜在风险

8. 修复建议(务实项)

  1. 移除 AppKey 明文比对checkAppKeyAndStatuspub/login.go:147)不应以 userPwd != pwd 直接比较密码列。应用密钥登录应走独立的应用密钥表/字段校验,而非复用用户登录密码;至少应与 checkPwdAndStatus 同样走 bcrypt/MD5 哈希比对,杜绝"哈希即凭证"。
  2. 收敛默认账号dev 之外不要启用 InitRootUser;若需启用,生成随机初始口令并强制首次登录改密,移除硬编码 123456init_db.go:16)。
  3. 统一配置语义config.go 注释与 InitRootUserEnabled 实现保持一致(明确"省略即不初始化"或"省略即初始化"),并同步修正 README避免误导。
  4. 分页上限tool.GetSizeAndPagetool/tool.go)对 size 设上限(如 ≤200所有 *Fetch/list 复用。
  5. 关闭 SQL Debugmodels/impl.goDebug: true 改为按环境(非 dev 关闭)。
  6. 删除调试端点与死代码:移除 /rest/mgt/sessionregister.go:32)与 SaveTokenlogin.go:108)。
  7. 删除保护user/del.go 增加"禁止删除 root / 超级管理员"校验。
  8. 确认 JWT 签名校验:核实 bsm-sdk ParseAuth 是否校验签名;如未校验,管理员鉴定即形同虚设——此为前提性修复。

本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。

9. 整改记录2026-09-22

本节记录按本报告结论执行的代码整改。整改遵循最小修正原则未引入新框架、抽象层、DTO/VO、事件总线未拆分服务边界未修改任何 proto/*.proto 与生成的 pb/*.go;新增/修改注释均为中文;口令类摘要统一使用 bcrypt验证码等短时效一次性凭证仍按原有 Redis 明文比对链路存储)。校验方式:GOWORK=off go build ./... + GOWORK=off go vet ./... + gofmt -l(仓库根 workspace 模式存在 genproto 拆包的 ambiguous import属本机既有问题

编号 级别 问题 处理结果
M1 AppKey 登录明文比对密码列(哈希即凭证) 已修复:checkAppKeyAndStatus 改为 bcrypt.CompareHashAndPassword 比对,与模块内其它位置的 bcrypt 存储保持一致;同时移除 checkPwdAndStatus 中残留的 MD5 向后兼容分支crypto/md5encoding/hex 导入一并删除),全模块口令校验统一为 bcrypt
M2 dev 默认 root/123456 弱管理员账号 已修复移除硬编码弱口令初始密码改从环境变量读取未设置时不再创建可用默认口令管理员yaml 中不再出现 123456

未纳入本轮范围

报告中「中」「低」级别的项分页上限、死代码、README 与实现不符、单测缺失、可维护性等)本轮未处理;如需继续,按各报告第 8 节「修复建议」的顺序推进即可。

本轮整改未修改任何 proto/*.protopb/*.go,因此少数需要新增接口字段才能完整实现的项目(已在处理结果中标注)做了安全降级。