数据安全之MySQL数据加解密的实现方案
2021/4/25 19:25:27
本文主要是介绍数据安全之MySQL数据加解密的实现方案,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
在我们日常的业务需求中,经常会遇到需要对存储的用户敏感数据进行加密处理的场景,如用户的身份信息、住址、身份证号等等,本文我们就讨论下,业务系统(后端)如何实现数据存储(基于MySQL)的加解密功能。
技术栈:springboot、mybatis、mysql等
方案一:基于spring aop拦截mybatis mapper.
第一步:定义注解@Encrypt
@Target(ElementType.METHOD)//注解的范围是类、接口、枚举的方法上 @Retention(RetentionPolicy.RUNTIME)//被虚拟机保存,可用反射机制读取 public @interface Encrypt{ /** * 入参需要加密的字段 * @return */ String[] paramFields() default {}; /** * 响应参数需解密的字段 * @return */ String[] respFields() default {}; }
第二步:开发拦截器处理类
@Slf4j @Aspect @Component public class EncryptAspect { @Pointcut("@annotation(com.xxx.annotation.Encrypt)") public void encryPointCut() { } @Around("encryPointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); final Sm4Intercept annotation = method.getAnnotation(Encrypt.class); if (null == annotation) { return joinPoint.proceed(); } //加密入参对象属性值 encryptRequest(joinPoint, annotation); //执行目标方法 Object response = joinPoint.proceed(); //解密响应结果对象的属性值 decryptResponse(response, annotation); return response; } /** * 加密请求入参对象 * * @param joinPoint * @param annotation */ private void encryptRequest(ProceedingJoinPoint joinPoint, Sm4Intercept annotation) { //获取接口的入参列表 final Object[] params = joinPoint.getArgs(); if (!CollectionUtil.isEmpty(params)) { //接口入参 Object param = params[0]; //接口入参对象的属性列表 Field[] fields = param.getClass().getDeclaredFields(); if (!CollectionUtil.isEmpty(annotation.paramFields())) { //遍历加密入参的属性值 Arrays.stream(annotation.paramFields()).forEach(target -> { Field field = Arrays.stream(fields) .filter(f -> f.getName().equals(target)) .findFirst() .orElse(null); if (null != field) { //反射获取目标属性值 Object fieldValue = getFieldValue(param, field.getName()); if (null != fieldValue) { String encryFieldValue = EncryptUtil.encryptEcb(key, fieldValue.toString()); log.info("类{}的属性{}的值{}已被加密为{}", param.getClass().getName(), target, fieldValue, encryFieldValue); setFieldValue(param, field.getName(), encryFieldValue); } } }); } } } /** * 解密响应结果对象的属性值 * * @param object * @param annotation */ private void decryptResponse(Object object, Sm4Intercept annotation) { //返回结果是list时 if (object instanceof List) { decryptListObject((List) object, annotation); return; } //返回结果为单对象时 decryptObject(object, annotation); } /** * 解密list中对象的属性值 * * @param list * @param annotation */ private void decryptListObject(List list, Sm4Intercept annotation) { list.stream().forEach(record -> decryptObject(record, annotation)); } /** * 解密单对象的属性值 * * @param record * @param annotation */ private void decryptObject(Object record, Sm4Intercept annotation) { //接口返回对象的属性列表 Field[] fields = record.getClass().getDeclaredFields(); if (!CollectionUtil.isEmpty(annotation.respFields())) { //遍历加密入参的属性值 Arrays.stream(annotation.respFields()).forEach(target -> { Field field = Arrays.stream(fields) .filter(f -> f.getName().equals(target)) .findFirst() .orElse(null); if (null != field) { //反射获取目标属性值 Object fieldValue = getFieldValue(record, field.getName()); if (null != fieldValue) { String decryFieldValue = EncryptUtil.decryptEcb(key, fieldValue.toString()); log.info("类{}的属性{}的值{}已被解密为{}", record.getClass().getName(), target, fieldValue, decryFieldValue); setFieldValue(record, field.getName(), decryFieldValue); } } }); } } /** * 通过反射,用属性名称获得属性值 * * @param thisClass 需要获取属性值的类 * @param fieldName 该类的属性名称 * @return */ private Object getFieldValue(Object thisClass, String fieldName) { Object value = new Object(); try { Method method = thisClass.getClass().getMethod(getMethodName(fieldName, "get")); value = method.invoke(thisClass); } catch (Exception e) { } return value; } /** * 通过反射,设置属性值 * * @param thisClass * @param fieldName * @param fieldValue */ private void setFieldValue(Object thisClass, String fieldName, Object fieldValue) { try { Method method = thisClass.getClass().getMethod(getMethodName(fieldName, "set"), String.class); method.invoke(thisClass, fieldValue); } catch (Exception e) { } } /** * 获取方法名称(getXXX,setXXX) * * @param fieldName * @param methodPrefix * @return */ private String getMethodName(String fieldName, String methodPrefix) { return methodPrefix.concat(fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1)); } }
第三步:mapper中定义注解@Encrypt进行数据拦截
@Mapper public interface UserInfoMapper extends BaseMapper<UserInfo> { /** * 如果查询条件中包含username,则在mapper执行前进行加密 * 如果返回数据中包含username及address等信息,则进行解密 * @param vo * @return */ @Encrypt(paramFields = {"userName"}, respFields = {"userName", "address"}) List<UserInfo> findUserInfo(UserSearchVo vo); }
这样,便实现了在数据查询(或更新、插入等)时,完成入参及返回数据的加解密操作。不过这种处理方式仅限于数据操作是通过Dao的mapper接口调用时,如果想处理更多场景,如通过mybatis-plus的Wraper方式进行数据处理时,则考虑用后面的第二种处理方式。
方案二:基于mybatis自带的扩展插件(plugins)实现
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
插件的使用参考实现如下(mybatis官方文档)
@Slf4j @Component @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class })}) public class ExamplePlugin implements Interceptor { private Properties properties = new Properties(); public Object intercept(Invocation invocation) throws Throwable { // implement pre processing if need Object returnObject = invocation.proceed(); // implement post processing if need return returnObject; } public void setProperties(Properties properties) { this.properties = properties; } }
即在mybatis的Executor的query方法执行前后进行拦截,如事先定义好需要加解密的“配置规则”(如“对哪个表的哪些字段需要加解密”、“方法的出入参需加解密的字段”等等),然后拦截sql的请求参数及执行的返回结果,对其进行相应的数据加解密操作。
本文的核心实现思路都是围绕spring aop进行实现的,足以说明aop思想的强大之处,大家平时学习工作中一定要勤学、多用、多练!希望本文可以帮助到有需要的朋友们!
这篇关于数据安全之MySQL数据加解密的实现方案的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-29阿里 Canal 实时同步 MySQL 增量数据至 ClickHouse 数据库
- 2024-05-24在Linux下管理MySQL的大小写敏感性
- 2024-04-26MySQL查出时间比实际晚8小时的解决方案
- 2024-04-01JPA不识别MySQL的枚举类型
- 2024-03-30mysql数据库表卡死解决方法
- 2024-03-15MySQL多数据源笔记5-ShardingJDBC实战
- 2024-03-11natural join mysql
- 2024-03-11关于VS2017,VS2015 中利用 EF使用Mysql 不显示数据源问题解决方案
- 2024-02-26mysql 阿里云xb后缀备份文件恢复-icode9专业技术文章分享
- 2024-02-22docker mysql 5.7