4.10 安全架构设计
概述
安全设计按威胁面组织:每项威胁对应明确控制措施与代码落点。本节给出威胁→控制总表、四条安全红线、部署基线与已知边界(诚实声明),可直接用于安全评审自查。
威胁 → 控制总表
| 威胁面 | 控制措施 | 代码落点 |
|---|---|---|
| 身份伪造 | HS256 签名校验 + iss/exp 强校验(限定 HS256 防算法混淆) | utility/jwt Parse |
| 令牌重放(登出后) | jti 黑名单,TTL=剩余有效期 | blacklist:{jti} |
| Refresh 重放 | 轮换:旧 refresh 入黑名单,已黑即拒(1008) | logic/auth Refresh |
| 被踢会话复活 | 强退同时拉黑 access+refresh 双令牌 | online_user ForceLogout |
| 口令窃取/泄露 | bcrypt(password+salt);日志脱敏(password/salt/token → ***) | utility/crypto; maskSecret |
| 暴力破解 | 登录/验证码/令牌兑换独立严阈值 10/min/IP | rateLimit.authLimit |
| 水平越权(跨租户) | dao.Ctx 注入 tenant_id IN(0,自身) | hooks.ApplyCtx |
| 行级越权(部门/本人) | 角色 data_scope 四档叠加过滤(仅列表/导出;写操作由 permission 兜底) | logic/data_scope.go |
| 垂直越权 | gmeta permission + HasPerm;超管放行 | Bind 包装层 |
| SQL 注入 | 全链 gdb 参数化;无方言拼接红线 | dao/Where 参数化 |
| 参数篡改 | gvalid 标签校验(Parse 统一触发) | api/v1 结构体标签 |
| 存储型 XSS | .svg 强制 attachment+nosniff;富文本外链不本地化、占位符契约 | middleware/response; base.go |
| SSRF | 富文本仅处理本站 URL,外站一律不抓取 | localizeOne 判定 |
| CSRF | Bearer 头令牌(非 Cookie)+ CORS 白名单 | JWT 中间件/CORS |
| IP 伪造 | 仅受信代理(api.proxyIps,支持 CIDR)的 XFF/X-Real-IP 生效 | realClientIP |
| 限流绕过 | 计数经 Redis 跨实例共享;窗口起点对齐 | RateLimit incrCounter |
| 越权写(演示站) | Demo 中间件拦截全部写操作 | middleware/demo |
| 审计缺失 | 操作日志异步全量记录(脱敏+响应+耗时) | OperationLog |
| 会话固定 | 无服务端 Cookie 会话,令牌全部服务端签发 | auth Login |
| 敏感信息出栈 | GetUser 永不返回 password/salt;令牌原文仅在线用户详情(需权限) | index/online_user |
四条租户隔离红线(安全审计重点)
红线清单
- 业务代码禁止
g.DB()直查业务表(绕过钩子与过滤) - 业务代码禁止手写 del_flag / tenant_id 条件(应由 dao.Ctx 注入)
- 新增租户级表必须登记 hooks.tenantTables(漏登=跨租户泄漏)
- 唯一预检范围必须等于唯一索引范围(否则 1062 漏报/误报)
另:平台级(超管)全量可见是设计预期,管理端点须收紧 permission。
文件上传安全链
text
大小限制 → 扩展名白名单 → 图片魔数嗅探(前 512 字节须 image/*)
→ 随机文件名(防猜路径/覆盖)
→ 临时/正式两段式(未过校验不进正式目录)
→ 静态响应 svg 附件化 + nosniff
→ 定期清理 temp(每日 03:30,tempExpireDays 默认 7 天)注意:fileExt 白名单含 js/css/sql 等——对公网用户提供上传时,建议按环境收紧 fileExt(配置层控制,无需改码)。
认证与授权安全细节
text
认证:
* 令牌 HS256;密钥/签发者可配置;exp 必填
* 双令牌分工:access 2h 只作身份;refresh 7d 仅 /api/oauth2/token 可用(typ 校验)
* 验证码:一次性(命中即删)、TTL 300s、bypassCode 生产必须留空
* 密码:bcrypt DefaultCost;外部 salt 防彩虹;旧 md5 数据被 IsBcrypt 拒绝(强制重置)
授权:
* 权限点粒度到接口级(sys:module:action),按钮级由前端控制;
新增接口必须声明 permission 或显式留空(留空=登录即可,评审需明确该选择)
* 隐藏节点:menu hide=1 的权限节点不进 Perms——"禁用权限"用 status 而非 hide
* 权限缓存一致性:保存权限/分配角色/改数据范围主动清 identity 缓存,最迟 300s 过期部署安全基线(生产)
text
1) 修改 jwt.secret 为高强度随机值且与环境隔离
2) captcha.bypassCode=""; app.demo=false; app.errorMsg=false; errorStack=false
3) database/redis 使用独立账号与内网地址;不暴露公网
4) api.proxyIps 只列真实反代出口;未用反代时保持默认(含 127.0.0.1)
5) file.fileExt 按业务收紧;uploadDir 权限最小化;domainUrl 走 CDN/域名隔离
6) rateLimit 按压测调参(防误伤;authLimit 不建议调大)
7) HTTPS 由前置代理终止;服务本身 HTTP 内网
8) 定期核对 /api.json 与权限点清单(防新接口漏配 permission)已知边界与取舍(诚实声明)
| 项 | 边界 |
|---|---|
| CheckUnique | 先查后插的 UX 预检,存在 TOCTOU——并发唯一性以 uk_* 索引为最终防线 |
| 数据权限 | 仅约束 Page/List/导出;Detail/Update/Delete 防线是 permission 节点 + 租户隔离 |
| 接线范围 | 数据权限当前仅 user 模块接线(dept_id 列);其他模块行级可见性由租户隔离单层兜底 |
| 部门子树 | deptSubtreeIds 每次全量拉部门表 BFS 无缓存——数百行内无感知,上万行需加短时缓存 |
| 身份缓存窗口 | 300s 内直接改库的 dept_id/data_scope 不经 API 不会即时生效(API 路径均主动清缓存) |
| 在线用户详情 | 返回令牌原文(便于运维排查),依赖 sys:onlineUser:detail 权限收口 |
| 日志截断 | param/result 截断 8KB,超长报文不完整留存 |
| 密码策略 | 无独立复杂度/定期更换强制(可按需在密码路径追加规则) |
| 防重放 | jti 黑名单依赖 TTL 自然过期,无物理防重放窗口 |