项目环境
- MySQL 8.4.10
- Redis 8.6.0
- JDK 21
- Maven 3.9.12
- SpringBoot 3.5.16
- MyBatisPlus 3.5.16
表创建SQL
ALTER TABLE `test`
ADD COLUMN `remark` varchar(255) NULL COMMENT '备注',
ADD COLUMN `deleted` int NULL COMMENT '是否删除',
ADD COLUMN `create_time` datetime NULL COMMENT '创建时间',
ADD COLUMN `create_user` varchar(255) NULL COMMENT '创建人',
ADD COLUMN `update_time` datetime NULL COMMENT '更新时间',
ADD COLUMN `update_user` varchar(255) NULL COMMENT '更新人';
已实现功能
- 自动代码模板生成,基于
Test - 程序启动安全握手,基于国密
SM2、SM4 - 接口验签、防重放、防重复提交,基于国密
SM3,防止中间人攻击和抓包 - 敏感字段加密解密,基于国密
SM4,防止中间人攻击 - 接口加解密,防止中间人攻击和抓包
- SM2+SM4混合实现:
- SM2:非对称加密,解决密钥分发问题
- SM4:对称加密,解决性能问题
- SM2+SM4混合实现:
- 添加时自动填充
deleted、createTime、updateTime、createUser、updateUser字段 - 更新时自动填充
updateTime、updateUser字段 - 响应数据中
id字段自动转为字符类型,防止精度丢失,同时配置了时间返回格式
进度
- 后端权限模型
- 接口加解密(支持等保)
权限模型设计
基于sa-token实现,RBAC(基于角色的访问控制)模型,多角色,多权限
安全问题
身份认证与访问控制 (核心)
- 多因素认证(MFA):登录时加入双因子验证(如TOTP动态验证码),满足密码复杂度+动态口令的组合要求
- RBAC细粒度权限:实现
StpInterface接口,从数据库动态加载用户角色和权限。通过@SaCheckPermission注解实现按钮级鉴权,支持AND/OR逻辑 - 会话安全管控:开启同端互斥登录(防止账号共享),实现“踢人下线”和账号封禁功能。严格使用Token,并配置
token-prefix: Bearer - 敏感操作二次验证:使用
@SaCheckSafe注解对关键操作进行二次身份确认
安全审计与日志 (必备)
- 操作日志完整性:记录所有增删改操作,使用SM2国密算法签名防篡改,满足“不可否认性”要求
- 登录日志审计:记录时间、IP、设备等信息,用于分析异常登录行为
- 全局异常处理:统一拦截
SaTokenException等鉴权异常,避免敏感信息泄露
数据安全与加密 (底线)
- 国密算法支持:敏感数据(密码、手机号)用SM4加密存储,传输使用SM2/SM4,适配国产化环境
- 传输通道安全:强制HTTPS,防止中间人攻击
- 敏感数据脱敏:返回数据时按规则掩码处理
接口与网络安全
- 防重放/防篡改:使用SaToken的参数签名模块,确保请求参数合法
- 接口防刷限流:对登录等核心接口限制调用频率
- 路由拦截鉴权:除注解外,使用
SaInterceptor设置全局拦截规则(如放行登录接口) - API Key支持:为外部调用签发独立API Key并分配最小权限
技术选型与扩展建议
- 用户类型扩展:利用SaToken多账号体系,区分
admin和user两套Token,实现前后台认证分离 - 分布式会话:集成 Redis 存储Token,保证集群部署时会话共享
- 国产化适配:预留接口,确保能适配达梦数据库、麒麟操作系统等
字典问题
在真实的开发中发现,很多状态都是数字类型,存在以下问题:
- 魔法值:代码中
if(status == 1)这样写,存在问题就是==魔法值==,代码不报错,运行时会导致运行时出现难以排查的Bug - 放在数据库未知问题:如果放数据库,字典是开放的,任何人(比如运维、DBA甚至前端)都可以随时往字典表里加一条
code=99, label="未知状态",此时业务代码里并没有处理99的逻辑,这会导致状态机流转出现致命漏洞或抛出异常 所以为了控制性,选用枚举比较好,能有效解决: - 魔法值问题
- 状态合法性问题
当然,对于接口的查询,这里需要用到反射,将定义的枚举以字典接口的形式给到请求
定义方式
先定义枚举接口
/**
* 枚举接口
*/
public interface BaseDictEnum {
/**
* 标识码
*/
Integer getCode();
/**
* 显示名称
*/
String getLabel();
}
然后根据业务实现该接口
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum OrderStatusEnum implements BaseDictEnum {
INIT(0, "待提交"),
APPROVING(1, "审核中"),
PASS(2, "已通过"),
REJECT(3, "已驳回");
private final Integer code;
private final String label;
}
骨架基础数据
基础表结构
管理后台的 超级管理员 会在项目启动时自动写入,因为密码需要加盐,默认 admin 和 123456
菜单表数据
需要手动同步,保持所有功能被看见
待办
- 系统用户、角色、菜单的关联性
- 操作日志
- 开发文档
功能列表
- 请求层
- 验签
- 身份认证
- 防篡改:确保请求在传输中没被中间人修改
- 证书(TLS)防的是传输过程中的中间人
- 验签防的是业务层面的冒充和篡改
- 两者防护的边界完全不同,是互补关系,不是替代关系
- 防重放
- 防止同一个合法请求被坏人反复利用
- 防重放的核心手段是让同一个请求只能有效一次
- timestamp(时间戳):请求带上当前时间,服务端只接受最近5分钟内的请求,超时直接拒绝,让黑客没时间慢慢重放
- nonce(随机数):一个只使用一次的随机字符串。服务端会缓存所有用过的nonce,同一个nonce出现第二次直接拒绝。确保即使请求在有效时间内,也不能重复使用
- 防重复提交
- 防御业务逻辑错误,防止用户因网络抖动、按钮连点等原因,在短时间内发起两次相同的合法操作(比如重复下单、重复转账)
- 加解密
- 验签
握手方式是先请求服务端拿到 SM4 密钥,用于生成签名和之后敏感字段加密
哪些字段算敏感字段?
可以遵循一个核心原则:任何泄露后会给用户或公司带来风险的字段,都应该被加密。 实践中,主要分为三类:
- 个人隐私(PII):手机号、身份证号、真实姓名、家庭住址、邮箱、银行卡号。
- 账号安全:密码(应哈希存储,而非加密)、支付密码、Token。
- 业务敏感:订单金额、合同编号、积分余额、用户画像标签(视具体业务定)。
实际开发中,通常在实体类上用注解来标记,比维护单独的“敏感字段列表”更直观、更易维护