前言
在实际的项目开发中,对了重要的数据表以及业务数据通常需要进行数据脱敏,数据记录完整性校验,部分重要业务字段的完整性校验,完整性校验先采用SM3获得加密摘要信息,再用mac算法加密摘要,最后将完整性加密结果记录到表字段今天就记录一下数据的完整性加密。
一、针对整条记录进行完整性加密
1.创建事件实体类
新建针对发布参数的不同事件实体类,实体类需要继承ApplicationEvent ,并构建一个有参构造方法。
针对传递参数为实体类的情景创建实体类UserRoleEvent4Entity :
@Data
public class UserRoleEvent4Entity extends ApplicationEvent {
private UserRole userRole;
public UserRoleEvent4Entity(Object source, UserRole userRole) {
super(source);
this.userRole = userRole;
}
}
针对传递参数为单个param参数的情景创建实体类UserRoleEvent4Id :
@Data
public class UserRoleEvent4Id extends ApplicationEvent {
private String id;
public UserRoleEvent4Id(Object source, String id) {
super(source);
this.id = id;
}
}
2.事件发布
serverImpl层业务逻辑中插入发布事件的代码,用到applicationContext的publishEvent方法。
针对发布内容为整个库表实体对象类型的部分代码如下:
@Override
@Transactional
public boolean addUserRoles(String uid, String orgId, List<String> roleIds, List<String> fileList) {
roleIds.forEach(roleId -> {
UserRole userRole = new UserRole();
userRole.setOrgId(orgId);
userRole.setUid(uid);
userRole.setRoleId(roleId);
userRole.setCreateUid(currentUid);
userRole.setUpdateUid(currentUid);
userRole.setStatus(1);
userRole.setFileIds(fileIds);
addRoles.add(userRole);
//发布事件
log.info("开始发布事件");
applicationContext.publishEvent(new UserRoleEvent4Entity(this,userRole));
});
saveBatch(addRoles);
}
针对发布内容为param参数类型的部分代码如下:
@Override
@Transactional
public boolean deleteUserRole(String uid, String orgId, String roleId) {
//update之前拿到主键id
UserRole userRole = this.query().eq(UserRole::getUid, uid).eq(UserRole::getOrgId, orgId)
.eq(UserRole::getRoleId, roleId).eq(UserRole::getStatus, CommonStatusEnum.NORMAL).getOne();
boolean result = false;
result = this.update()
.eq(UserRole::getUid, uid)
.eq(UserRole::getOrgId, orgId)
.eq(UserRole::getRoleId, roleId)
.eq(UserRole::getStatus, CommonStatusEnum.NORMAL)
.set(UserRole::getStatus, CommonStatusEnum.DISABLE)
.set(UserRole::getUpdateUid, ApiUtils.currentUid())
.set(UserRole::getUpdateTime, LocalDateTime.now())
.execute();
//发布事件
if (ObjectUtils.isNotEmpty(userRole)){
log.info("开始发布事件");
applicationContext.publishEvent(new UserRoleEvent4Id(this,userRole.getId()));
}
return result;
}
3.事件监听处理
controller层监听事件内容,获取传递的参数,对相关信息进行完整性加密。
@EventListener
@Async //异步处理防止阻塞流程
public void acceptUserRole(UserRoleEvent4Entity userRoleEvent4Entity){
log.info("开始监听事件,并处理事件结果:"+LocalDateTime.now());
try {
UserRole userRole = userRoleEvent4Entity.getUserRole();
if (ObjectUtils.isNotEmpty(userRole)){
//先SM3再进行mac加密
String SM3Digest = csspUtil.getSM3Digest(JSONUtil.toJsonStr(userRole));
userRole.setMacDigest(csspUtil.getMACDigest(SM3Digest));
userRoleService.updateById(userRole);
}
log.info("结束事件监听:"+LocalDateTime.now());
}catch (Exception e){
log.error("事件处理异常:"+e.toString());
throw new ApiException(new ErrorCode(false,"事件处理出现异常!"));
}
}
方法上加上@EventListener注解监听业务代码中发布的事件,加上@Async 注解进行异步处理,防止阻塞业务流程。
@EventListener
@Async //异步处理防止阻塞流程
public void acceptUserRoleId(UserRoleEvent4Id userRoleEvent4Id){
log.info("开始监听事件,并处理事件结果:"+LocalDateTime.now());
try {
String id = userRoleEvent4Id.getId();
if (StringUtils.isNotEmpty(id)){
log.info("获取到主键id为:"+ id);
//先睡10s让业务逻辑走完再查数据,否则查回的为旧数据
Thread.sleep(10000);
UserRole userRole = userRoleService.getById(id);
//先SM3再进行mac加密
String SM3Digest = csspUtil.getSM3Digest(JSONUtil.toJsonStr(userRole));
userRole.setMacDigest(csspUtil.getMACDigest(SM3Digest));
userRoleService.updateById(userRole);
}
log.info("结束事件监听:"+LocalDateTime.now());
}catch (Exception e){
log.error("事件处理异常:"+e.toString());
throw new ApiException(new ErrorCode(false,"事件处理出现异常!"));
}
}
此处代码中Thread.sleep(10000);进程睡了10s中才查询数据,是为了等业务代码执行完毕,再去查询到最新的数据进行完整性加密处理。
二、对部分重要字段信息进行完整性加密
1.user用户信息实体类:
部分代码如下:
@Slf4j
@TableName("module_sys_user")
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class User extends BaseModelUUID {
private static final long serialVersionUID = 1L;
@ApiModelProperty(notes = "用户唯一编码")
private String userIdCode;
@ApiModelProperty(notes = "用户中文名")
private String name;
@ApiModelProperty(notes = "登录英文名")
private String loginName;
@ApiModelProperty(notes = "身份证")
private String idcardNumber;
@ApiModelProperty(notes = "联系电话")
private String phone;
@ApiModelProperty(notes = "mac加密摘要,用于数据完整性校验")
private String MacDigest;
@TableField(exist = false)
private Boolean isMac = false;
@TableField(exist = false)
private Boolean isEncrypt = false;
public String getIdcardNumber() {
//如果是加密后沒入库,保存数据直接返回密文
if(isEncrypt) {
return idcardNumber;
}
return ApplicationUtils.getBean(CsspUtil.class).SM4Decrypt(idcardNumber);
}
public void setIdcardNumber(String idcardNumber) {
this.idcardNumber = idcardNumber;
}
public void setPhone(String phone) {
this.phone = phone;
//生成完整性校验的待加密串
if (this.isMac){
String str = this.userIdCode+this.name+this.loginName+this.idcardNumber+this.phone;
//SM3获取摘要
String SM3Digest = ApplicationUtils.getBean(CsspUtil.class).getSM3Digest(str);
//进行mac加密
String mac = ApplicationUtils.getBean(CsspUtil.class).getMACDigest(SM3Digest);
log.info("用户信息完整性校验mac值: "+mac);
this.setMacDigest(mac);
}
}
}
2.serverImpl实现类部分代码:
User userInfo = new User();
userInfo.setUserIdCode(userIdCode);
userInfo.setName(username);
userInfo.setLoginName(account);
userInfo.setStatus(1);
userInfo.setIsMac(true);
userInfo.setPhone(mobilenumber);
this.saveOrUpdate(userInfo);
为了避免其他的业务逻辑也进入到完整性加密的代码逻辑,从而影响代码执行效率,这里定义isMac标识符,当需要对业务数据内容进行完整性加密时,把isMac标识设置为true,进入到加密逻辑中。