Files
full/docs/passport.md
2026-09-22 18:53:53 +08:00

14 KiB
Raw Blame History

passport用户账号与身份认证代码审计报告

内容
审计对象 module/base/passport
服务域 基础与平台服务
审计日期 2026-09-22
代码规模 手写 Go 文件约 44 个(含 cmd/internal/service/proto/ 6 个);pb/ 下约 21 个生成 .go 仅看签名
入口 cmd/maingRPC + grpc-gateway 单进程)、cmd/cli(调试入口)、聚合入口 pkgs/allpkgs/ecmall
对外协议 gRPCpassport.Account/Login/Register/Forget/Verify+ RESTgrpc-gateway路径形如 POST /passport.Login/Pwd
结论摘要 认证核心存在致命缺陷:验证码登录/注册/找回均不校验验证码;找回密码仅凭 identity 即可改密且以未加盐 MD5 存储;密码哈希使用 bcrypt.MinCostcost=4KYC 回调为空 stubToken 缓存键前缀大小写不一致。

1. 服务定位与职责

用户通行证账号、密码、第三方登录、KYC 认证、标签、统计)的统一身份服务。负责账号注册、密码/验证码登录、密码找回、KYC 验证回调、用户资料与标签管理。它是认证与账户存储服务,不负责网关鉴权本身(鉴权依赖上层网关注入 metadataservice.ParseMetaCtx 读取)。

2. 代码结构与入口

路径 职责
cmd/main/main.go 独立进程入口:配置→初始化 impl→建 gRPC/Gateway→server.New(nil)service.New 启动
cmd/cli/main.go 调试入口:仅打印数据库配置
internal/config/config.go 配置结构(Base/Databases/MicroService/Gateway/Token/Kyc/WeChat 等)与校验;设置 JWT 过期
internal/impl/impl.go 初始化 Redis、DB、Etcd、内存缓存实例
internal/vars/{token,status,provider}.go 常量:TokenPrefix="/TOKEN/"、账号状态、第三方 provider
internal/excode/ex.go 空文件,无内容
internal/models/* 11 个模型 + query.go(InitData/Exists) + cache.go(Redis 缓存与验证码接口)
internal/logic/common/token.go env.Runtime.JwtSecretKey 签发 JWT
internal/logic/{login,register,forget,account,verify}/* 各业务方法实现
internal/server/* gRPC server 注册5 个 service 全部委托 logic
service/{expose,dependencies}.go 聚合宿主注入接口(供 pkgs/all 复用共享连接)
proto/*.proto 5 个服务契约(google.api.http 注解REST 路径由网关生成)

依赖注入:service.Dependencies 支持外部传入 Redis/Etcd/DB/Cache非 nil 时覆盖 internal/impl 对象,供聚合入口复用共享连接。

3. 接口清单

proto 中未声明 ping/hello 服务,但 etc/*.yamlMicroService.Anonymous 列出 passport.ping.hello,与当前模块实现不一致(疑似残留)。

方法 路径(gRPC / REST) 功能 鉴权 实现位置
Login.Pwd passport.Login/Pwd 账号+密码登录 否(公开) logic/login/pwd.go
Login.Code passport.Login/Code 验证码登录 否(公开) logic/login/code.go
Login.Quick passport.Login/Quick 验证码快捷登录并注册 否(公开) logic/login/quick.go
Register.Pwd passport.Register/Pwd 账号密码注册 否(公开) logic/register/{pwd,do}.go
Register.Code passport.Register/Code 手机验证码注册 否(公开) logic/register/{code,do}.go
Forget.Verify passport.Forget/Verify 验证手机号和验证码 否(公开) logic/forget/verify.go
Forget.Reset passport.Forget/Reset 重置密码 否(公开) logic/forget/reset.go
Account.Get passport.Account/Get 获取完整用户信息 是(ParseMetaCtx logic/account/get.go
Account.SetData passport.Account/SetData 更新用户资料 logic/account/set_data.go
Account.SetPassword passport.Account/SetPassword 修改密码 logic/account/set_password.go
Account.TagCreate passport.Account/TagCreate 新增标签 logic/account/tag_create.go
Account.TagRemove passport.Account/TagRemove 删除标签 logic/account/tag_remove.go
Account.Statistics passport.Account/Statistics 获取统计数据 logic/account/statistics.go
Verify.Request passport.Verify/Request 发起 KYC 验证 logic/verify/request.go
Verify.JumioCallback passport.Verify/JumioCallback KYC 回调 否(公开) logic/verify/jumio_callback.go

全部声明的 gRPC 方法均已委托到 logicserver 层无 Unimplemented 未实现项);但 Verify.JumioCallbackForget.Verify逻辑占位/伪实现(见 6.3)。

4. 数据模型与表

表名 字段 类型 键/约束 说明
passport_account id uint PK 自增主键
identity varchar - 用户唯一码(对外公开)
account varchar(255) 默认 '' 账号
phone varchar(20) 默认 '' 手机号
email varchar(255) 默认 '' 邮箱
password varchar(255) not null 密码bcrypt重置时误写为 MD5
salt varchar(255) not null 密码盐
passport_data passport_id/passport_identity - - 关联账号
nickname/avatar/sign/cover varchar 默认 '' 资料
sex/birthday int8/time - 性别/生日
country/province/city/area varchar 默认 '' 地区
score/level int32 默认 0 积分/等级
rights varchar(255) 默认 '' 权限
email_verify/phone_verify/face_verify/document_verify/kyc_verify int32 默认 0 各验证状态
passport_tags passport_id/name/icon - - 用户标签
passport_verify passport_id + 各验证时间/证件/KYC 字段 - - 认证详情KYC 回调未写入)
passport_provider provider/provider_id/access_token/refresh_token - - 第三方登录绑定
passport_score / passport_statistics / passport_rights_expiry / passport_notify 见各模型文件 - - 积分/统计/特权有效期/消息

5. 核心流程

flowchart TD
    A["POST passport.Login/Code"] --> B["校验 phone/code/country 非空"]
    B --> C["GetPassportAccountByField(phone)"]
    C --> D["直接签发 JWT 返回"]
    D --> E["未比对 Redis 验证码"]
    F["POST passport.Forget/Reset"] --> G["校验 identity/password 非空"]
    G --> H["Update password = Md5(pwd)"]
    H --> I["账号被接管/密码无法登录"]
    J["POST passport.Login/Pwd"] --> K["bcrypt.CompareHashAndPassword"]
    K --> L["签发 JWT 并写 Redis(/TOKEN/)"]
    M["POST passport.Register/Pwd"] --> N["bcrypt.MinCost 哈希+盐"]
    N --> O["Create account + data"]

6. 审计发现

6.1 安全

级别 位置 问题
internal/logic/login/code.go:16-59 验证码登录不校验验证码。仅判断 in.Phone/in.Code/in.Country 非空后直接 GetPassportAccountByField("phone",...) 并签发 JWT从未调用 GetVerificationCodeCache 比对 in.Code。任意已知手机号 + 任意 code 即可登录。
internal/logic/login/quick.go:20-64 快捷登录并注册同样不校验 code,且当手机号不存在时(:32-64自动创建账号。攻击者可用任意 code 对任意手机号批量建号并登录。
internal/logic/forget/reset.go:17-41 找回密码无验证码/令牌校验。仅凭 identity + passwordUpdate("password", ...)。前置 Forget.Verify 也未真正校验 code见 6.3)。identity 在登录/注册响应中公开返回,攻击者可据此直接接管任意账号。
internal/logic/forget/reset.go:31 以未加盐 MD5 存储密码Update("password", utils.Md5(in.Password)),与全模块 bcrypt 体系不一致;且登录校验走 bcryptlogin/pwd.go:27),重置后的密码既不安全又无法用于登录(逻辑自相矛盾)。
internal/logic/register/do.go:30internal/logic/account/set_password.go:35internal/models/query.go:21 密码哈希 cost 过低:使用 bcrypt.MinCost=4而非默认 10抗暴力破解能力弱。
internal/vars/token.go:3 vs internal/logic/register/do.go:84 Token 缓存键前缀不一致vars.TokenPrefix="/TOKEN/" 用于 login/pwd.go:48login/code.go:41;而 register/do.go:84config.Spec.Token.Prefixyaml 为 /token/)。同一 identity 在 Redis 中形成两把不同键,登出/校验可能取错或失效。
internal/logic/login/code.go:41 写 Redis token 时 TTL 传 0(永不过期),与 login/pwd.go:48_vars.JwtExpire 不一致;缓存 token 永久留存(仅依赖 JWT 自身过期)。
test/grpc/main.go:45 硬编码测试 JWTauthorization 明文 tokenid/identity)出现在测试代码,若被复用可越权。

6.2 正确性与逻辑缺陷

级别 位置 问题
internal/logic/account/get.go:44 impl.DBService.Where("passport_id=?", AUTH.ID).First(data) 未处理 error;查询失败或记录不存在时 data 仍是零值指针(非 nilif data != nil 恒真,后续 data.Birthday.Format 等执行DB 错误被吞掉、返回空资料。
internal/logic/forget/reset.go:31 重置密码不同步更新 saltpassport_account.salt 列存在),且写入 MD5与 bcrypt 校验路径不兼容(同 6.1)。
internal/logic/register/do.go:53,70 账号与扩展数据两次 Create 非事务:若第二步失败,会留下无扩展数据的孤儿账号。
internal/logic/register/code.go:13-31 Code 注册仅校验手机号格式与唯一性,未校验 in.Code,与"验证码注册"语义不符(同 6.1 高)。

6.3 未完成实现

  • internal/logic/verify/jumio_callback.go:13-35KYC 回调printer.Info 打印完整 payload 后返回成功,未校验回调签名、未更新 passport_verify 的 KYC 状态——典型占位实现。
  • internal/logic/forget/verify.go:14-32:名义"验证手机号和验证码",实际不比对 code(无任何 Redis 验证码查询),属伪实现。
  • internal/models/query.go:12-39InitData 计划初始化 demo/welcome 默认账号(密码 bcrypt MinCostcmd/main/main.go:37 已注释未启用;含硬编码默认口令,属潜在隐患(一旦启用即成后门)。
  • internal/models/cache.go:144-172IncrementLoginAttempts/ClearLoginAttempts 从未被调用README 声称的"内置限流"未落地。
  • internal/models/cache.go:124-142SetVerificationCodeCache/GetVerificationCodeCache 提供验证码存取,但所有登录/注册/找回逻辑均未调用,验证码形同虚设(与 6.1 高关联)。
  • internal/excode/ex.go:空文件,错误码未定义。
  • cmd/cli/main.go:仅打印 DB 配置,无实际用途(调试残留)。
  • etc/*.yamlSecretKey: CHANGE_ME 字段在 config.goSrvConfig 无对应字段,未被解析;真实 JWT 密钥来自 env.Runtime.JwtSecretKeylogic/common/token.go:10),其初始化不在本模块可见范围【信息不足】。
  • 全模块*_test.go 单元测试

6.4 健壮性与可维护性

级别 位置 问题
internal/logic/account/get.go:44 DB 错误未处理(同 6.2)。
internal/logic/login/*forget/reset.goverify/jumio_callback.go 多处 printer.Error/Info 输出内部错误与敏感参数(如 jumio_callback.go:21 打印完整 KYC 回调含 PII、reset.go:33 打印 identity、login/pwd.go:34 打印 DB 错误),存在信息泄露。
internal/vars/token.go:3 vs config Token 前缀两套定义(见 6.1)。
全模块 无单元测试;InitData/CreateAccount 等存在死代码或未被调用路径。

7. 风险汇总

编号 级别 问题 影响面
P1 验证码登录不校验 code 任意手机号可登录,认证失效
P2 快捷登录/注册不校验 code 且自动建号 批量伪造账号
P3 找回密码仅凭 identity 改密、无校验 任意账号被接管
P4 重置密码以未加盐 MD5 存储且与 bcrypt 不兼容 密码泄露风险 + 重置后无法登录
P5 bcrypt.MinCostcost=4 密码暴力破解成本降低
P6 Token 缓存键前缀大小写不一致 登录态缓存错乱/登出失效
P7 KYC 回调空 stub、Forget.Verify 伪实现 认证状态不可信
P8 验证码缓存接口从未被调用 全链路验证码机制失效
P9 内部错误/PII 经日志泄露 信息泄露
P10 DB 错误未处理、无事务、无测试 健壮性与可维护性

8. 修复建议(务实项)

  1. 验证码必须校验:在 login/code.gologin/quick.goregister/code.goforget/verify.go 中调用 GetVerificationCodeCache(phone) 并比对 in.Code(一次一消);forget/reset.go 必须要求先通过 Verify 且校验一次性令牌,再允许改密。
  2. 重置密码用 bcryptforget/reset.go:31 改为 bcrypt.GenerateFromPassword([]byte(newPwd+salt), bcrypt.DefaultCost) 并同步更新 salt,与登录校验一致。
  3. 提高哈希 costregister/do.goaccount/set_password.goquery.gobcrypt.MinCost 改为 bcrypt.DefaultCost10
  4. 统一 Token 前缀:仅保留一处定义(建议用 config.Spec.Token.Prefixloginregister 共用,避免 /TOKEN//token/ 双键。
  5. KYC 回调落地jumio_callback.go 增加签名校验并更新 passport_verifykyc_verify/kyc_statusForget.Verify 真正比对验证码。
  6. 收敛默认账号:保持 InitData 不启用,或删除该函数与硬编码 welcome 口令。
  7. 错误处理account/get.go:44 处理 First 的 errorregister/do.go 两次 Create 用事务包裹。
  8. 清理:移除 cmd/cli 调试输出、空 excode/ex.go、未调用的限流函数(或真正接入);敏感信息勿进 printer

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