概要
注解加拦截器实现自动加解密和数据完整性校验
一些敏感信息存入数据需要进行加密处理,比如电话号码,身份证号码等,从数据库取出到前端展示时需要解密
一些重要数据不可被篡改。
流程
SELECT时对添加了@SensitiveEntity注解的实体中添加了@SecurityField注解的字段type=CONFIDENTIALITY进行解密type=INTEGRITY进行完整性校验
UPDATE、INSERT时,对添加了@SensitiveEntity注解的实体中添加了@SecurityField注解的字段type=CONFIDENTIALITY进行加密type=INTEGRITY进行完整性保护
技术细节
实体注解@SensitiveEntity
/**
* 敏感实体注解
* 需要在实体类上添加
* @author lq
* @date 2024/07/30
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityEntity {
}
字段注解@SecurityField
/**
* 敏感字段注解
* 需要在字段上添加
* @author lq
* @date 2024/07/30
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityField {
SecurityType[] type() default SecurityType.CONFIDENTIALITY;
}
注解操作类型SecurityType
/**
* 注解操作类型
* INTEGRITY完整 CONFIDENTIALITY机密 SIGNATURE签名
* @author lq
* @date 2024/07/30
**/
public enum SecurityType {
INTEGRITY,
CONFIDENTIALITY,
SIGNATURE;
}
MyBatis拦截器
/**
* 加解密、完整性拦截器
*
* @author lq
* @date 2024/07/30
**/
@Component
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})
public class SecurityInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(SecurityInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
Object parameter = invocation.getArgs()[1];
logger.info("------SecurityInterceptor.sqlCommandType------{}" + sqlCommandType);
if (parameter == null) {
return invocation.proceed();
}
//查询时,做完整性校验和解密
if (SqlCommandType.SELECT == sqlCommandType) {
//取出查询的结果
Object returnValue = invocation.proceed();
if (oConvertUtils.isEmpty(returnValue)) {
return null;
}
// TODO 这里returnValue如果是List或者Map,要特殊处理
if (isSensitiveEntity(returnValue)) {
try {
//验证完整性
validateEntityIntegrity(returnValue);
} catch (Exception e) {
validateFail(returnValue);
return returnValue;
}
//如果完整性验证不通过,就没必要做解密操作了。
try {
//解密
entityDecrypt(returnValue);
}catch (Exception e){
logger.error("解密失败:{}",e);
}
}
return returnValue;
}
//新增或修改时,做加密和完整性保护
if (SqlCommandType.UPDATE == sqlCommandType || SqlCommandType.INSERT == sqlCommandType) {
if (parameter instanceof MapperMethod.ParamMap) {
MapperMethod.ParamMap<?> p = (MapperMethod.ParamMap<?>) parameter;
String et = "et";
if (p.containsKey(et)) {
parameter = p.get(et);
} else {
parameter = p.get("param1");
}
if (parameter == null) {
return invocation.proceed();
}
}
if (isSensitiveEntity(parameter)) {
//加密
entityEncrypt(parameter);
//完整性保护
entityIntegrity(parameter);
}
return invocation.proceed();
}
return invocation.proceed();
}
/**
* 完整性保护
*
* @param obj
*/
private void entityIntegrity(Object obj) throws IllegalAccessException {
Field[] fields = obj.getClass().getDeclaredFields();
List<String> list = new LinkedList<>();
for (Field field : fields) {
if (isSecurityField(field)) {
String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
field.setAccessible(true);
Object value = field.get(obj);
field.setAccessible(false);
if (types.contains(SecurityType.INTEGRITY.toString())) {
if (oConvertUtils.isNotEmpty(value)) {
list.add(value.toString());
}
}
}
}
if (oConvertUtils.listIsNotEmpty(list)) {
String hmacValue = getHmacValue(list);
for (Field field : fields) {
if (isSecurityField(field)) {
String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
if (types.contains(SecurityType.SIGNATURE.toString())) {
field.setAccessible(true);
field.set(obj, hmacValue);
field.setAccessible(false);
}
}
}
}
}
/**
* 验证完整性
*
* @param obj
*/
private boolean validateEntityIntegrity(Object obj) throws IllegalAccessException {
Field[] fields = obj.getClass().getDeclaredFields();
List<String> list = new LinkedList<>();
String hmacValue = "";
for (Field field : fields) {
if (isSecurityField(field)) {
String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
field.setAccessible(true);
Object value = field.get(obj);
field.setAccessible(false);
if (types.contains(SecurityType.INTEGRITY.toString())) {
if (oConvertUtils.isNotEmpty(value)) {
list.add(value.toString());
}
}
if (types.contains(SecurityType.SIGNATURE.toString())) {
if (oConvertUtils.isNotEmpty(value)) {
hmacValue = value.toString();
}
}
}
}
if (oConvertUtils.listIsEmpty(list) || oConvertUtils.isEmpty(hmacValue)) {
//不进行完整性验证
return false;
}
boolean isValidate = this.validate(list, hmacValue);
return isValidate;
}
private boolean validate(List<String> list, String hmacValue) {
String hmacValue2 = getHmacValue(list);
if (hmacValue.equals(hmacValue2)) {
return true;
} else {
throw new RuntimeException("数据完整性校验失败");
}
}
/**
* 对需要完整性保护的字段计算得到HmacValue
*
* @param list
* @return {@link String }
*/
private String getHmacValue(List<String> list) {
String originalValue = StringUtils.join(list, "::");
byte[] originalByte = originalValue.getBytes(StandardCharsets.UTF_8);
//这里是我用的计算HAMC方法,可替换自己的
SecretKey key = SymmetricOperationUtil.exportInternalKey(10);
byte[] hmacByte = SymmetricOperationUtil.integrityHmac(key.getEncoded(), originalByte);
String hmacValue2 = Base64.getEncoder().encodeToString(hmacByte);
return hmacValue2;
}
/**
* 完整性验证失败
*
* @param obj
* @throws IllegalAccessException
*/
private void validateFail(Object obj) throws IllegalAccessException {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (isSecurityField(field)) {
String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
if (types.contains(SecurityType.INTEGRITY.toString())) {
field.setAccessible(true);
//数据被篡改时,将数据改为"数据被篡改"作为提示。
field.set(obj, "数据被篡改");
field.setAccessible(false);
}
}
}
}
/**
* 加密
*
* @param obj
*/
private void entityEncrypt(Object obj) throws IllegalAccessException {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (isSecurityField(field)) {
String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
if (types.contains(SecurityType.CONFIDENTIALITY.toString())) {
logger.info("类:{},加密字段:{}", obj.getClass(), field.getName());
field.setAccessible(true);
Object value = field.get(obj);
if (oConvertUtils.isNotEmpty(value)) {
//这里是我用的加密操作,可以替换为自己的加密操作。
byte[] indata = value.toString().getBytes(StandardCharsets.UTF_8);
String encData = SymmetricOperationUtil.internalSm4Enc(1, "CBC", true, indata, new byte[16]);
field.set(obj, encData);
}
field.setAccessible(false);
}
}
}
}
/**
* 解密
*
* @param obj
*/
private void entityDecrypt(Object obj) throws IllegalAccessException {
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (isSecurityField(field)) {
String types = StringUtils.join(field.getAnnotation(SecurityField.class).type(), ",");
if (types.contains(SecurityType.CONFIDENTIALITY.toString())) {
field.setAccessible(true);
Object value = field.get(obj);
field.setAccessible(false);
if (oConvertUtils.isNotEmpty(value)) {
logger.info("{}:value:{}", field.getName(), value);
String decStr;
try {
//这里是我用的解密操作,可以替换为自己的解密操作。
byte[] indata = Base64.getDecoder().decode(value.toString());
decStr = SymmetricOperationUtil.internalSm4Dec(1, "CBC", true, indata, new byte[16]);
}catch (Exception e){
logger.info("解密失败");
decStr = "解密失败";
}
field.setAccessible(true);
field.set(obj, decStr);
field.setAccessible(false);
}
}
}
}
}
/**
* 是否包含注解SensitiveEntity
*
* @param obj
* @return boolean
*/
private boolean isSensitiveEntity(Object obj) {
if (oConvertUtils.isEmpty(obj)) {
return false;
}
return obj.getClass().isAnnotationPresent(SecurityEntity.class);
}
/**
* 是否包含注解SensitiveField
*
* @param field
* @return boolean
*/
private boolean isSecurityField(Field field) {
if (oConvertUtils.isEmpty(field)) {
return false;
}
return field.isAnnotationPresent(SecurityField.class);
}
}
小结
只需要在对应的实体上增加相应的注解,即可实现
@SecurityEntity
public class AppUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(type = IdType.ASSIGN_ID)
@ApiModelProperty(value = "id")
private String id;
/**
* 用户名
*/
@Excel(name = "用户名", width = 15)
@ApiModelProperty(value = "用户名")
private String loginName;
/**
* 手机号
*/
@Excel(name = "手机号", width = 15)
@ApiModelProperty(value = "手机号")
@SecurityField(type = {SecurityType.CONFIDENTIALITY, SecurityType.INTEGRITY})
private String phoneNo;
/**
* 身份证号码
*/
@Excel(name = "身份证号码", width = 15)
@ApiModelProperty(value = "身份证号码")
@SecurityField(type = {SecurityType.CONFIDENTIALITY, SecurityType.INTEGRITY})
private String idCardNo;
/**
* HMAC值
*/
@SecurityField(type = SecurityType.SIGNATURE)
private String hmacValue;
}
新增编辑AppUser ,可以对相应的字段进行完整性保护和加密
查询AppUser 时会对相应字段进行完整性校验和解密。
不支持对缓冲数据进行操作